From c698b1ef9c75e1ebb2af3be3d4b7b776b6949411 Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Tue, 4 Mar 2025 10:06:24 -0500 Subject: [PATCH] docs(core): maintain ts monorepos feature (#30256) Adds the Maintain TypeScript Monorepos feature page --- docs/generated/manifests/menus.json | 16 + docs/generated/manifests/nx.json | 22 ++ docs/generated/manifests/tags.json | 187 +++++++----- docs/map.json | 6 + .../features/maintain-typescript-monorepos.md | 284 ++++++++++++++++++ docs/shared/reference/sitemap.md | 1 + .../src/lib/tags/graph.component.tsx | 6 +- .../src/lib/tags/side-by-side.component.tsx | 16 +- .../src/lib/tags/side-by-side.schema.ts | 6 +- 9 files changed, 455 insertions(+), 89 deletions(-) create mode 100644 docs/shared/features/maintain-typescript-monorepos.md diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index f45cd17d83..a02707e629 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -235,6 +235,14 @@ "children": [], "disableCollapsible": false }, + { + "name": "Maintain TypeScript Monorepos", + "path": "/features/maintain-ts-monorepos", + "id": "maintain-ts-monorepos", + "isExternal": false, + "children": [], + "disableCollapsible": false + }, { "name": "Automate Updating Dependencies", "path": "/features/automate-updating-dependencies", @@ -359,6 +367,14 @@ "children": [], "disableCollapsible": false }, + { + "name": "Maintain TypeScript Monorepos", + "path": "/features/maintain-ts-monorepos", + "id": "maintain-ts-monorepos", + "isExternal": false, + "children": [], + "disableCollapsible": false + }, { "name": "Automate Updating Dependencies", "path": "/features/automate-updating-dependencies", diff --git a/docs/generated/manifests/nx.json b/docs/generated/manifests/nx.json index 61c3385995..179efb70b5 100644 --- a/docs/generated/manifests/nx.json +++ b/docs/generated/manifests/nx.json @@ -317,6 +317,17 @@ "path": "/features/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", "name": "Automate Updating Dependencies", @@ -488,6 +499,17 @@ "path": "/features/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": { "id": "automate-updating-dependencies", "name": "Automate Updating Dependencies", diff --git a/docs/generated/manifests/tags.json b/docs/generated/manifests/tags.json index 05d58e74a6..cc48a29667 100644 --- a/docs/generated/manifests/tags.json +++ b/docs/generated/manifests/tags.json @@ -382,6 +382,110 @@ "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": [ { "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" } ], - "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": [ { "description": "", @@ -748,52 +815,6 @@ "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": [ { "description": "", diff --git a/docs/map.json b/docs/map.json index 794c1b4b81..fae4d21276 100644 --- a/docs/map.json +++ b/docs/map.json @@ -101,6 +101,12 @@ "tags": ["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", "description": "Learn how Nx provides automated update scripts to help you keep your workspace, tooling and framework dependencies up to date.", diff --git a/docs/shared/features/maintain-typescript-monorepos.md b/docs/shared/features/maintain-typescript-monorepos.md new file mode 100644 index 0000000000..9b7f8c3c9f --- /dev/null +++ b/docs/shared/features/maintain-typescript-monorepos.md @@ -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" %} +/// +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 +``` diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index 91df7e149a..6f01d7cedf 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -16,6 +16,7 @@ - [Enhance Your LLM](/features/enhance-AI) - [Explore Your Workspace](/features/explore-graph) - [Generate Code](/features/generate-code) + - [Maintain TypeScript Monorepos](/features/maintain-ts-monorepos) - [Automate Updating Dependencies](/features/automate-updating-dependencies) - [Enforce Module Boundaries](/features/enforce-module-boundaries) - [Manage Releases](/features/manage-releases) diff --git a/nx-dev/ui-markdoc/src/lib/tags/graph.component.tsx b/nx-dev/ui-markdoc/src/lib/tags/graph.component.tsx index 7a7f3a7a75..b89517e5d5 100644 --- a/nx-dev/ui-markdoc/src/lib/tags/graph.component.tsx +++ b/nx-dev/ui-markdoc/src/lib/tags/graph.component.tsx @@ -69,7 +69,7 @@ export function Graph({ if (!jsonFile && !parsedProps) { if (!children || !children.hasOwnProperty('props')) { return ( -
+

No JSON provided for graph, use JSON code fence to embed data for the graph. @@ -82,7 +82,7 @@ export function Graph({ setParsedProps(JSON.parse(children?.props.children as any)); } catch { return ( -

+

Could not parse JSON for graph:

{children?.props.children as any}
@@ -94,7 +94,7 @@ export function Graph({ } return ( -
+
{title}
diff --git a/nx-dev/ui-markdoc/src/lib/tags/side-by-side.component.tsx b/nx-dev/ui-markdoc/src/lib/tags/side-by-side.component.tsx index 941eefffc2..f9809f1568 100644 --- a/nx-dev/ui-markdoc/src/lib/tags/side-by-side.component.tsx +++ b/nx-dev/ui-markdoc/src/lib/tags/side-by-side.component.tsx @@ -1,9 +1,21 @@ +import { cx } from '@nx/nx-dev/ui-primitives'; 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); return ( -
+
{first}
{rest}
diff --git a/nx-dev/ui-markdoc/src/lib/tags/side-by-side.schema.ts b/nx-dev/ui-markdoc/src/lib/tags/side-by-side.schema.ts index 099d894e0f..3d856e2e15 100644 --- a/nx-dev/ui-markdoc/src/lib/tags/side-by-side.schema.ts +++ b/nx-dev/ui-markdoc/src/lib/tags/side-by-side.schema.ts @@ -2,5 +2,9 @@ import { Schema } from '@markdoc/markdoc'; export const sideBySide: Schema = { render: 'SideBySide', - attributes: {}, + attributes: { + align: { + type: 'String', + }, + }, };