-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Description
Current Behavior
When @nx/esbuild:esbuild runs in a TS Solution Setup monorepo with skipTypeCheck: true and declaration: false, the esbuild executor still calls runTypeCheck due to this condition in esbuild.impl.ts:
if (!options.skipTypeCheck || options.isTsSolutionSetup) {The || options.isTsSolutionSetup clause forces type checking to run for ALL TS Solution Setup projects, regardless of skipTypeCheck. When declaration: false, the type check runs in noEmit mode with ignoreDiagnostics: true (lines 257-259 of esbuild.impl.ts), making it completely pointless — no declarations emitted, no diagnostics reported.
However, this pointless type check has a harmful side effect: TypeScript's program.emit() still writes a minimal 19-byte tsbuildinfo file ({"version":"5.9.3"}) when the project's tsconfig inherits composite: true (which is always true in TS Solution Setup, since isUsingTsSolutionSetup() requires composite: true in tsconfig.base.json).
This causes a race condition when typecheck and build targets run in parallel:
build(esbuild) finishes first → callsrunTypeCheckinnoEmitmode → writes poisoned 19-byte tsbuildinfotypecheck(tsc --build) sees the recent tsbuildinfo timestamp → considers project "up-to-date" → skips.d.tsemission- Downstream projects (e.g.,
tsconfig.spec.json) fail with TS6305 because.d.tsfiles were never emitted
This is non-deterministic — different projects fail on each run depending on timing. CI may pass because typecheck and build typically run in separate jobs (clean environments), but local development with nx run-many --targets=typecheck,build --parallel fails consistently.
Expected Behavior
When skipTypeCheck: true and declaration: false, the esbuild executor should not run type checking at all in TS Solution Setup. The isTsSolutionSetup override should only force type checking when declarations actually need to be generated.
Root Cause Analysis
The code flow that creates the problem:
normalize.ts(lines 59-77): When user setsdeclaration: falseexplicitly, normalization correctly keepsskipTypeCheck: trueesbuild.impl.ts(line 195):|| options.isTsSolutionSetupoverridesskipTypeCheck, forcingrunTypeCheckesbuild.impl.ts(lines 238-244): Sincedeclarationisfalse, mode isnoEmitesbuild.impl.ts(lines 257-259): SinceisTsSolutionSetup && skipTypeCheck, setsignoreDiagnostics: truerun-type-check.ts(line 143): SetsnoEmit: truebut inheritscomposite: truefrom tsconfigrun-type-check.ts(line 106):program.emit()writes a 19-byte tsbuildinfo despitenoEmit: true(TypeScript behavior withcomposite: true)
The type check in step 2-5 does nothing useful (no declarations, no diagnostics) — it only produces the harmful tsbuildinfo file.
Proposed Fix (Two-Part)
Primary fix: Skip the unnecessary type check (esbuild.impl.ts)
// Line 195 (non-watch mode)
- if (!options.skipTypeCheck || options.isTsSolutionSetup) {
+ if (!options.skipTypeCheck || (options.isTsSolutionSetup && options.declaration)) {
// Lines 139-140 (watch mode)
if (
!options.skipTypeCheck ||
- options.isTsSolutionSetup
+ (options.isTsSolutionSetup && options.declaration)
) {This ensures type checking is only forced in TS Solution Setup when declarations actually need to be generated. When skipTypeCheck: true AND declaration: false, the type check is skipped entirely — which is exactly what the user requested and has no downsides.
Defense-in-depth: Prevent tsbuildinfo writes in noEmit mode (run-type-check.ts)
- : { noEmit: true };
+ : { noEmit: true, composite: false };Setting composite: false alongside noEmit: true prevents TypeScript from writing tsbuildinfo files even if runTypeCheck is called in noEmit mode from other code paths.
Steps to Reproduce
- Create an NX workspace with TS Solution Setup
- Set
composite: truein roottsconfig.base.json - Configure at least one project with
@nx/esbuild:esbuildexecutor,skipTypeCheck: true,declaration: false - Run
nx run-many --targets=typecheck,build --all --parallel=16 - Observe non-deterministic TS6305 errors on various projects
Nx Report
Node : 24.11.1
OS : darwin-arm64
pnpm : 10.26.2
nx : 22.5.0
@nx/js : 22.5.0
@nx/esbuild : 22.5.0
typescript : 5.9.3
Failure Logs
error TS6305: Output file '/path/to/project/out-tsc/app/src/domain/SomeEntity.d.ts' has not been built from source file '/path/to/project/src/domain/SomeEntity.ts'.
Multiple TS6305 errors appear across different projects on each run (non-deterministic).
Additional Information
- We are using a pnpm patch as a workaround with both fixes applied
- The root cause is that
isTsSolutionSetupforces a type check that has no useful output whendeclaration: falseandskipTypeCheck: true