external/ag-shared/prompts/skills/nx-performance/references/build-and-dev.md
The recommended pattern splits build into four sub-targets that maximise parallelism and minimise cache invalidation:
build (nx:noop aggregator)
├── build:types (@nx/js:tsc) → dist/types/*.d.ts
├── build:package (@nx/esbuild) → dist/package/*.cjs.js + *.esm.mjs
├── build:umd (@nx/esbuild) → dist/umd/*.js (depends on build:package)
└── build:test (tsc) → dist/test/** (depends on build:types)
build:types and build:package run simultaneously — no mutual dependency.build:types but not build:package (if the JS output is unchanged). Changing implementation invalidates build:package but not build:types.build:umd uses jsOutputs (not production), so it only rebuilds when actual JS output changes — not when comments or tests change.| Change type | build:types | build:package | build:umd | build:test |
|---|---|---|---|---|
| Source code change | Rebuilds | Rebuilds | Only if JS output changed | Rebuilds |
| Test file change | Cached | Cached | Cached | Rebuilds |
| Comment-only change | Rebuilds | Cached | Cached | Rebuilds |
Individual packages can override targetDefaults. For example, a community package (e.g., ag-charts-community) may override build:umd to build from source rather than dist/ output, because its UMD bundle is a self-contained browser bundle needing a dedicated entry point (main-umd.ts).
"build:umd": {
"dependsOn": [],
"inputs": ["production", "^production"],
"options": { "main": "{projectRoot}/src/main-umd.ts" }
}
build a monolithic target? If a single target runs tsc + bundle + UMD, every change invalidates everything.build:umd depend on build:package output (not source)? The UMD bundle should consume dist/package/main.cjs.js, not src/. Some packages may legitimately override this.build:test depending on build:types (not build:package) means tests can compile as soon as types are ready.Use @nx/esbuild:esbuild for build:package and build:umd. esbuild is 10-100x faster than tsc + bundler for producing JS output. @nx/js:tsc is only used for build:types (declaration emit).
Custom esbuild plugins (esbuild.config.cjs) handle: CSS inlining and minification, HTML minification, post-build .min.js generation, UMD wrapper adaptation.
For targets that run hundreds of times with identical setup (e.g., ~200 example generation tasks), batch executors avoid spawning a separate Node process per task:
Both implementation and batchImplementation must be declared in executors.json.
batchImplementation in executors.json?nx dev
├── Phase 1: Dependencies (sequential)
│ ├── ^build:types (all packages)
│ ├── ^build:package (all packages)
│ └── generate (all examples + thumbnails)
└── Phase 2: Concurrent processes
├── watch.js (file watcher + queue-based rebuilder)
└── <product>-website:dev (Astro dev server)
Variants:
dev:lite — simple HTTPS file server instead of Astro. Much faster startup for core library work.dev:quick — only depends on a single thumbnail generation. Fastest startup for gallery-focused work.The watch script is a queue-based system:
nx watch --all detects file changes and emits project namesbuild:umd listed before build so browser-reloadable target finishes firstnx run-many) prevents command-line length issuesKey environment variables:
NX_FORCE_REUSE_CACHED_GRAPH=true — skips re-computing the project graph on every watch-triggered build (~20-40ms savings per invocation)NX_DAEMON=true — required for the watch process (even if daemon is disabled for one-shot commands)BUILD_FWS — opt-in to rebuild framework wrappers during watch (excluded by default)Git-aware pausing: Builds are blocked during git rebase, git merge, or any operation that creates .git/index.lock.
dev depend on build but NOT lint/test? Dev startup should be minimal.build:umd first means reload fires sooner.NX_FORCE_REUSE_CACHED_GRAPH set? Avoids re-computing the project graph on every watch-triggered build.// build:umd only needs JS outputs, not types
"build:umd": {
"dependsOn": ["build:package", "^build:package"],
"inputs": ["jsOutputs"]
}
// build:test needs types (for compilation) but not packages (not bundling)
"build:test": {
"dependsOn": ["^build:types", "build:types"]
}
// test needs build:test (compiled specs) + build:package (runtime)
"test": {
"inputs": ["default", "buildOutputExcludes", "^production"]
}
test depending on build (which includes UMD).^ (upstream) dependencies used correctly? ^build:types means "build types for all dependencies". Without ^, it means "build types for this project only".all)AG product monorepos use an aggregator project with nx:noop targets and dependsOn: ["^targetName"] to fan out work. Running nx run all:build triggers every package's build.
Convenience targets: blt (build-lint-test), blt:ci (adds e2e and pack), clean, nuke.
nx run-many with hand-maintained project lists that drift.AG product monorepos use custom Nx plugins (e.g., ag-charts-task-autogen) that scan example directories (e.g., packages/*/src/**/_examples/*/main.ts) and create virtual projects at graph-computation time, each with generate-example, generate-thumbnail, and typecheck targets.
project.json files?createNodes API