docs(core): maintain ts monorepos feature (#30256)

Adds the Maintain TypeScript Monorepos feature page
This commit is contained in:
Isaac Mann 2025-03-04 10:06:24 -05:00 committed by GitHub
parent 8e6c00719b
commit c698b1ef9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 455 additions and 89 deletions

View File

@ -235,6 +235,14 @@
"children": [], "children": [],
"disableCollapsible": false "disableCollapsible": false
}, },
{
"name": "Maintain TypeScript Monorepos",
"path": "/features/maintain-ts-monorepos",
"id": "maintain-ts-monorepos",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{ {
"name": "Automate Updating Dependencies", "name": "Automate Updating Dependencies",
"path": "/features/automate-updating-dependencies", "path": "/features/automate-updating-dependencies",
@ -359,6 +367,14 @@
"children": [], "children": [],
"disableCollapsible": false "disableCollapsible": false
}, },
{
"name": "Maintain TypeScript Monorepos",
"path": "/features/maintain-ts-monorepos",
"id": "maintain-ts-monorepos",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{ {
"name": "Automate Updating Dependencies", "name": "Automate Updating Dependencies",
"path": "/features/automate-updating-dependencies", "path": "/features/automate-updating-dependencies",

View File

@ -317,6 +317,17 @@
"path": "/features/generate-code", "path": "/features/generate-code",
"tags": ["generate-code"] "tags": ["generate-code"]
}, },
{
"id": "maintain-ts-monorepos",
"name": "Maintain TypeScript Monorepos",
"description": "",
"mediaImage": "",
"file": "shared/features/maintain-typescript-monorepos",
"itemList": [],
"isExternal": false,
"path": "/features/maintain-ts-monorepos",
"tags": ["inferred-tasks", "project-linking", "sync"]
},
{ {
"id": "automate-updating-dependencies", "id": "automate-updating-dependencies",
"name": "Automate Updating Dependencies", "name": "Automate Updating Dependencies",
@ -488,6 +499,17 @@
"path": "/features/generate-code", "path": "/features/generate-code",
"tags": ["generate-code"] "tags": ["generate-code"]
}, },
"/features/maintain-ts-monorepos": {
"id": "maintain-ts-monorepos",
"name": "Maintain TypeScript Monorepos",
"description": "",
"mediaImage": "",
"file": "shared/features/maintain-typescript-monorepos",
"itemList": [],
"isExternal": false,
"path": "/features/maintain-ts-monorepos",
"tags": ["inferred-tasks", "project-linking", "sync"]
},
"/features/automate-updating-dependencies": { "/features/automate-updating-dependencies": {
"id": "automate-updating-dependencies", "id": "automate-updating-dependencies",
"name": "Automate Updating Dependencies", "name": "Automate Updating Dependencies",

View File

@ -382,6 +382,110 @@
"path": "/nx-api/nx/documents/generate" "path": "/nx-api/nx/documents/generate"
} }
], ],
"inferred-tasks": [
{
"description": "",
"file": "shared/features/maintain-typescript-monorepos",
"id": "maintain-ts-monorepos",
"name": "Maintain TypeScript Monorepos",
"path": "/features/maintain-ts-monorepos"
},
{
"description": "",
"file": "shared/concepts/mental-model",
"id": "mental-model",
"name": "Mental Model",
"path": "/concepts/mental-model"
},
{
"description": "",
"file": "shared/concepts/nx-plugins",
"id": "nx-plugins",
"name": "What Are Nx Plugins",
"path": "/concepts/nx-plugins"
},
{
"description": "",
"file": "shared/concepts/inferred-tasks",
"id": "inferred-tasks",
"name": "Inferred Tasks",
"path": "/concepts/inferred-tasks"
},
{
"description": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred"
},
{
"description": "",
"file": "shared/recipes/plugins/project-graph-plugins",
"id": "project-graph-plugins",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins"
}
],
"project-linking": [
{
"description": "",
"file": "shared/features/maintain-typescript-monorepos",
"id": "maintain-ts-monorepos",
"name": "Maintain TypeScript Monorepos",
"path": "/features/maintain-ts-monorepos"
},
{
"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"
}
],
"sync": [
{
"description": "",
"file": "shared/features/maintain-typescript-monorepos",
"id": "maintain-ts-monorepos",
"name": "Maintain TypeScript Monorepos",
"path": "/features/maintain-ts-monorepos"
},
{
"description": "",
"file": "shared/concepts/sync-generators",
"id": "sync-generators",
"name": "Sync Generators",
"path": "/concepts/sync-generators"
},
{
"description": "",
"file": "shared/recipes/generators/create-sync-generator",
"id": "create-sync-generator",
"name": "Create a Sync Generator",
"path": "/extending-nx/recipes/create-sync-generator"
},
{
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
"file": "generated/packages/nx/documents/sync",
"id": "sync",
"name": "sync",
"path": "/nx-api/nx/documents/sync"
},
{
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
"file": "generated/packages/nx/documents/sync-check",
"id": "sync-check",
"name": "sync:check",
"path": "/nx-api/nx/documents/sync-check"
}
],
"automate-updating-dependencies": [ "automate-updating-dependencies": [
{ {
"description": "Learn how Nx provides automated update scripts to help you keep your workspace, tooling and framework dependencies up to date.", "description": "Learn how Nx provides automated update scripts to help you keep your workspace, tooling and framework dependencies up to date.",
@ -621,43 +725,6 @@
"path": "/concepts/mental-model" "path": "/concepts/mental-model"
} }
], ],
"inferred-tasks": [
{
"description": "",
"file": "shared/concepts/mental-model",
"id": "mental-model",
"name": "Mental Model",
"path": "/concepts/mental-model"
},
{
"description": "",
"file": "shared/concepts/nx-plugins",
"id": "nx-plugins",
"name": "What Are Nx Plugins",
"path": "/concepts/nx-plugins"
},
{
"description": "",
"file": "shared/concepts/inferred-tasks",
"id": "inferred-tasks",
"name": "Inferred Tasks",
"path": "/concepts/inferred-tasks"
},
{
"description": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred"
},
{
"description": "",
"file": "shared/recipes/plugins/project-graph-plugins",
"id": "project-graph-plugins",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins"
}
],
"add": [ "add": [
{ {
"description": "", "description": "",
@ -748,52 +815,6 @@
"path": "/nx-api/nx/documents/daemon" "path": "/nx-api/nx/documents/daemon"
} }
], ],
"sync": [
{
"description": "",
"file": "shared/concepts/sync-generators",
"id": "sync-generators",
"name": "Sync Generators",
"path": "/concepts/sync-generators"
},
{
"description": "",
"file": "shared/recipes/generators/create-sync-generator",
"id": "create-sync-generator",
"name": "Create a Sync Generator",
"path": "/extending-nx/recipes/create-sync-generator"
},
{
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
"file": "generated/packages/nx/documents/sync",
"id": "sync",
"name": "sync",
"path": "/nx-api/nx/documents/sync"
},
{
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
"file": "generated/packages/nx/documents/sync-check",
"id": "sync-check",
"name": "sync:check",
"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": [ "module-federation": [
{ {
"description": "", "description": "",

View File

@ -101,6 +101,12 @@
"tags": ["generate-code"], "tags": ["generate-code"],
"file": "shared/features/generate-code" "file": "shared/features/generate-code"
}, },
{
"name": "Maintain TypeScript Monorepos",
"id": "maintain-ts-monorepos",
"tags": ["inferred-tasks", "project-linking", "sync"],
"file": "shared/features/maintain-typescript-monorepos"
},
{ {
"name": "Automate Updating Dependencies", "name": "Automate Updating Dependencies",
"description": "Learn how Nx provides automated update scripts to help you keep your workspace, tooling and framework dependencies up to date.", "description": "Learn how Nx provides automated update scripts to help you keep your workspace, tooling and framework dependencies up to date.",

View File

@ -0,0 +1,284 @@
# Maintain TypeScript Monorepos
Keeping all the industry-standard tools involved in a large TypeScript monorepo correctly configured and working well together is a difficult task. And the more tools you add, the more opportunity there is for tools to conflict with each other in some way.
In addition to [generating default configuration files](/features/generate-code) and [automatically updating dependencies](/features/automate-updating-dependencies) to versions that we know work together, Nx makes managing all the tools in your monorepo easier in two ways:
- Rather than adding another tool that you have to configure, Nx configures itself to match the existing configuration of other tools.
- Nx also enhances certain tools to be more usable in a monorepo context.
## Auto-Configuration
Whenever possible, Nx will detect the existing configuration settings of other tools and update itself to match.
### Project Detection with Workspaces
If your repository is using package manager workspaces, Nx will use those settings to find all the [projects](/reference/project-configuration) in your repository. So you don't need to define a project for your package manager and separately identify the project for Nx. The `workspaces` configuration on the left allows Nx to detect the project graph on the right.
{% side-by-side align="top" %}
```json {% fileName="/package.json" %}
{
"workspaces": ["apps/*", "packages/*"]
}
```
{% graph height="200px" title="Project View" %}
```json
{
"composite": false,
"projects": [
{
"name": "product-state",
"type": "lib",
"data": {
"root": "packages/cart/product-state",
"tags": ["scope:cart", "type:state"]
}
},
{
"name": "ui-buttons",
"type": "lib",
"data": {
"root": "packages/ui/buttons",
"tags": ["scope:shared", "type:ui"]
}
},
{
"name": "cart",
"type": "app",
"data": {
"root": "apps/cart",
"tags": ["type:app", "scope:cart"]
}
}
],
"dependencies": {
"product-state": [],
"ui-buttons": [],
"cart": [
{ "source": "cart", "target": "product-state", "type": "static" },
{ "source": "cart", "target": "ui-buttons", "type": "static" }
]
},
"workspaceLayout": {
"appsDir": "apps",
"libsDir": "libs"
},
"affectedProjectIds": [],
"focus": null,
"groupByFolder": false,
"exclude": [],
"enableTooltips": false
}
```
{% /graph %}
{% /side-by-side %}
### Inferred Tasks with Tooling Plugins
Nx provides [plugins](/concepts/nx-plugins) for tools that run tasks, like Vite, TypeScript, Playwright or Jest. These plugins can automatically [infer the Nx-specific task configuration](/concepts/inferred-tasks) based on the tooling configuration files that already exist.
In the example below, because the `/apps/cart/vite.config.ts` file exists, Nx knows that the `cart` project can run a `build` task using Vite. If you expand the `build` task on the right, you can also see that Nx configured the output directory for the [cache](/features/cache-task-results) to match the `build.outDir` provided in the Vite configuration file.
With inferred tasks, you can keep your tooling configuration file as the one source of truth for that tool's configuration, instead of adding an extra layer of configuration on top.
{% side-by-side align="top" %}
```ts {% fileName="/apps/cart/vite.config.ts" %}
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/cart',
plugins: [react()],
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});
```
{% project-details %}
```json
{
"project": {
"name": "cart",
"type": "app",
"data": {
"root": "apps/cart",
"targets": {
"build": {
"options": {
"cwd": "apps/cart",
"command": "vite build"
},
"cache": true,
"dependsOn": ["^build"],
"inputs": [
"production",
"^production",
{
"externalDependencies": ["vite"]
}
],
"outputs": ["{projectRoot}/dist"],
"executor": "nx:run-commands",
"configurations": {},
"metadata": {
"technologies": ["vite"]
}
}
},
"name": "cart",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/cart/src",
"projectType": "application",
"tags": [],
"implicitDependencies": [],
"metadata": {
"technologies": ["react"]
}
}
},
"sourceMap": {
"root": ["apps/cart/project.json", "nx/core/project-json"],
"targets": ["apps/cart/project.json", "nx/core/project-json"],
"targets.build": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.command": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.options": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.cache": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.dependsOn": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.inputs": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.outputs": ["apps/cart/vite.config.ts", "@nx/vite/plugin"],
"targets.build.options.cwd": [
"apps/cart/vite.config.ts",
"@nx/vite/plugin"
],
"name": ["apps/cart/project.json", "nx/core/project-json"],
"$schema": ["apps/cart/project.json", "nx/core/project-json"],
"sourceRoot": ["apps/cart/project.json", "nx/core/project-json"],
"projectType": ["apps/cart/project.json", "nx/core/project-json"],
"tags": ["apps/cart/project.json", "nx/core/project-json"]
}
}
```
{% /project-details %}
{% /side-by-side %}
## Enhance Tools for Monorepos
Nx does not just reduce its own configuration burden, it also improves the functionality of your existing tools so that they work better in a monorepo context.
### Keep TypeScript Project References in Sync
TypeScript provides a feature called [Project References](https://www.typescriptlang.org/docs/handbook/project-references.html) that allows the TypeScript compiler to build and typecheck each project independently. When each project is typechecked, the TypeScript compiler will output an intermediate `*.tsbuildinfo` file that can be used by other projects instead of re-typechecking all dependencies. This feature can provide [significant performance improvements](/concepts/typescript-project-linking#typescript-project-references-performance-benefits), particularly in a large monorepo.
The main downside of this feature is that you have to manually define each project's references (dependencies) in the appropriate `tsconfig.*.json` file. This process is tedious to set up and very difficult to maintain as the repository changes over time. Nx can help by using a [sync generator](/concepts/sync-generators) to automatically update the references defined in the `tsconfig.json` files based on the project graph it already knows about.
{% side-by-side align="top" %}
{% graph height="200px" title="Project View" %}
```json
{
"composite": false,
"projects": [
{
"name": "product-state",
"type": "lib",
"data": {
"root": "packages/cart/product-state",
"tags": ["scope:cart", "type:state"]
}
},
{
"name": "ui-buttons",
"type": "lib",
"data": {
"root": "packages/ui/buttons",
"tags": ["scope:shared", "type:ui"]
}
},
{
"name": "cart",
"type": "app",
"data": {
"root": "apps/cart",
"tags": ["type:app", "scope:cart"]
}
}
],
"dependencies": {
"product-state": [],
"ui-buttons": [],
"cart": [
{ "source": "cart", "target": "product-state", "type": "static" },
{ "source": "cart", "target": "ui-buttons", "type": "static" }
]
},
"workspaceLayout": {
"appsDir": "apps",
"libsDir": "libs"
},
"affectedProjectIds": [],
"focus": null,
"groupByFolder": false,
"exclude": [],
"enableTooltips": false
}
```
{% /graph %}
```jsonc {% fileName="apps/cart/tsconfig.json" %}
{
"extends": "../../tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY NX SYNC
// All project dependencies
{
"path": "../../packages/product-state"
},
{
"path": "../../packages/ui/buttons"
},
// This project's other tsconfig.*.json files
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
```
{% /side-by-side %}
Later, if someone adds another dependency to the `cart` app and then runs the `build` task, Nx will detect that the project references are out of sync and ask if the references should be updated.
```text {% command="nx build cart" path="~/myorg" %}
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …
Yes, sync the changes and run the tasks
No, run the tasks without syncing the changes
```

View File

@ -16,6 +16,7 @@
- [Enhance Your LLM](/features/enhance-AI) - [Enhance Your LLM](/features/enhance-AI)
- [Explore Your Workspace](/features/explore-graph) - [Explore Your Workspace](/features/explore-graph)
- [Generate Code](/features/generate-code) - [Generate Code](/features/generate-code)
- [Maintain TypeScript Monorepos](/features/maintain-ts-monorepos)
- [Automate Updating Dependencies](/features/automate-updating-dependencies) - [Automate Updating Dependencies](/features/automate-updating-dependencies)
- [Enforce Module Boundaries](/features/enforce-module-boundaries) - [Enforce Module Boundaries](/features/enforce-module-boundaries)
- [Manage Releases](/features/manage-releases) - [Manage Releases](/features/manage-releases)

View File

@ -69,7 +69,7 @@ export function Graph({
if (!jsonFile && !parsedProps) { if (!jsonFile && !parsedProps) {
if (!children || !children.hasOwnProperty('props')) { if (!children || !children.hasOwnProperty('props')) {
return ( return (
<div className="no-prose my-6 block rounded-md bg-red-50 p-4 text-red-700 ring-1 ring-red-100 dark:bg-red-900/30 dark:text-red-600 dark:ring-red-900"> <div className="no-prose block rounded-md bg-red-50 p-4 text-red-700 ring-1 ring-red-100 dark:bg-red-900/30 dark:text-red-600 dark:ring-red-900">
<p className="mb-4"> <p className="mb-4">
No JSON provided for graph, use JSON code fence to embed data for No JSON provided for graph, use JSON code fence to embed data for
the graph. the graph.
@ -82,7 +82,7 @@ export function Graph({
setParsedProps(JSON.parse(children?.props.children as any)); setParsedProps(JSON.parse(children?.props.children as any));
} catch { } catch {
return ( return (
<div className="not-prose my-6 block rounded-md bg-red-50 p-4 text-red-700 ring-1 ring-red-100 dark:bg-red-900/30 dark:text-red-600 dark:ring-red-900"> <div className="not-prose block rounded-md bg-red-50 p-4 text-red-700 ring-1 ring-red-100 dark:bg-red-900/30 dark:text-red-600 dark:ring-red-900">
<p className="mb-4">Could not parse JSON for graph:</p> <p className="mb-4">Could not parse JSON for graph:</p>
<pre className="p-4 text-sm">{children?.props.children as any}</pre> <pre className="p-4 text-sm">{children?.props.children as any}</pre>
</div> </div>
@ -94,7 +94,7 @@ export function Graph({
} }
return ( return (
<div className="my-6 w-full place-content-center overflow-hidden rounded-md ring-1 ring-slate-200 dark:ring-slate-700"> <div className="w-full place-content-center overflow-hidden rounded-md ring-1 ring-slate-200 dark:ring-slate-700">
<div className="relative flex justify-center border-b border-slate-200 bg-slate-100/50 p-2 font-bold dark:border-slate-700 dark:bg-slate-700/50"> <div className="relative flex justify-center border-b border-slate-200 bg-slate-100/50 p-2 font-bold dark:border-slate-700 dark:bg-slate-700/50">
{title} {title}
</div> </div>

View File

@ -1,9 +1,21 @@
import { cx } from '@nx/nx-dev/ui-primitives';
import { Children, ReactNode } from 'react'; import { Children, ReactNode } from 'react';
export function SideBySide({ children }: { children: ReactNode }) { export function SideBySide({
align,
children,
}: {
align: string;
children: ReactNode;
}) {
const [first, ...rest] = Children.toArray(children); const [first, ...rest] = Children.toArray(children);
return ( return (
<div className="not-prose grid items-center divide-x divide-solid divide-slate-50 md:grid-cols-2 dark:divide-slate-800"> <div
className={cx(
'not-prose grid divide-x divide-solid divide-slate-50 md:grid-cols-2 dark:divide-slate-800',
align === 'top' ? 'items-start' : 'items-center'
)}
>
<div className="md:pr-6">{first}</div> <div className="md:pr-6">{first}</div>
<div className="md:pl-6">{rest}</div> <div className="md:pl-6">{rest}</div>
</div> </div>

View File

@ -2,5 +2,9 @@ import { Schema } from '@markdoc/markdoc';
export const sideBySide: Schema = { export const sideBySide: Schema = {
render: 'SideBySide', render: 'SideBySide',
attributes: {}, attributes: {
align: {
type: 'String',
},
},
}; };