From 562ec50d2bfe29d588740e7b2e49842792579055 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:07:35 +0000 Subject: [PATCH 1/6] fix(@angular/build): enforce library compilerOptions and skip unused .tsbuildinfo emit Ensure library compilations explicitly set `noEmit: false`, `emitDeclarationOnly: false`, and `emitDecoratorMetadata: false` in `transformCompilerOptions` so that configurations extending a root `tsconfig.json` with `noEmit` or `emitDeclarationOnly` still emit both JavaScript and declaration files. Additionally, skip the application-specific `target < ES2022` Browserslist warning and `useDefineForClassFields = false` fallback when compiling libraries, and avoid serializing and writing on-disk `.tsbuildinfo` files during library builds since library outputs are emitted into in-memory maps on cold builds and watch rebuilds reuse the in-memory `BuilderProgram` directly. --- .../library/tests/behavior/build_spec.ts | 31 +++++++++++++- .../compilation/angular-compilation_spec.ts | 27 ++++++++++++ .../angular/compilation/compiler-options.ts | 42 +++++++++++-------- 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts index 0b6de2a26c7f..6c7906210c31 100644 --- a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts +++ b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts @@ -237,7 +237,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => ); }); - it('should write and reuse .tsbuildinfo in the persistent cache directory when cache is enabled', async () => { + it('should succeed without emitting .tsbuildinfo in the persistent cache directory when cache is enabled', async () => { harness.useProject('lib', { root: 'projects/lib', sourceRoot: 'projects/lib/src', @@ -258,7 +258,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => expect(firstBuild.result?.success).toBeTrue(); harness .expectFile('.angular/cache/0.0.0-PLACEHOLDER/lib/angular-build-library/.tsbuildinfo') - .toExist(); + .toNotExist(); await harness.writeFile( 'projects/lib/src/public-api.ts', @@ -278,5 +278,32 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => expect(dtsContent).toContain('LibComponent'); expect(dtsContent).toContain('CACHED_BUILD'); }); + + it('should override noEmit and emitDeclarationOnly in tsconfig.lib.json and emit both JS and .d.ts files', async () => { + await harness.modifyFile('projects/lib/tsconfig.lib.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions = { + ...tsconfig.compilerOptions, + noEmit: true, + emitDeclarationOnly: true, + emitDecoratorMetadata: true, + }; + + return JSON.stringify(tsconfig, null, 2); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.error).toBeUndefined(); + expect(result?.success).toBeTrue(); + + const fesmContent = harness.readFile('dist/lib/fesm2022/lib.mjs'); + expect(fesmContent).toContain('LibComponent'); + const dtsContent = harness.readFile('dist/lib/types/lib.d.ts'); + expect(dtsContent).toContain('LibComponent'); + }); }); }); diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts index 58c492ac5214..f253e2140bf6 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts @@ -479,5 +479,32 @@ describe('AngularCompilation', () => { expect(compilerOptions.noEmitOnError).toBe(false); expect(compilerOptions.composite).toBe(false); }); + + it('enforces library compiler options and skips app-only target warning and .tsbuildinfo', () => { + const { compilerOptions, warnings } = transformCompilerOptions( + ts, + { + target: ts.ScriptTarget.ES2020, + noEmit: true, + emitDeclarationOnly: true, + emitDecoratorMetadata: true, + isolatedModules: true, + }, + { + cachePath: '/tmp/cache', + }, + 'tsconfig.lib.json', + 'library', + ); + + expect(compilerOptions.target).toBe(ts.ScriptTarget.ES2022); + expect(compilerOptions.useDefineForClassFields).toBeUndefined(); + expect(compilerOptions.noEmit).toBe(false); + expect(compilerOptions.emitDeclarationOnly).toBe(false); + expect(compilerOptions.emitDecoratorMetadata).toBe(false); + expect(compilerOptions.incremental).toBe(false); + expect(compilerOptions.tsBuildInfoFile).toBeUndefined(); + expect(warnings).toEqual([]); + }); }); }); diff --git a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts index 77bfdfde2f90..22e0cff30bde 100644 --- a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts +++ b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts @@ -37,11 +37,12 @@ export function transformCompilerOptions( ): { compilerOptions: ng.CompilerOptions; warnings: PartialMessage[] } { const compilerOptions = { ...baseCompilerOptions }; const warnings: PartialMessage[] = []; - const isLibrary = buildType === 'library'; + const isLibrary = buildType === 'library' || !!baseCompilerOptions.declaration; if ( - compilerOptions.target === undefined || - compilerOptions.target < typeScript.ScriptTarget.ES2022 + !isLibrary && + (compilerOptions.target === undefined || + compilerOptions.target < typeScript.ScriptTarget.ES2022) ) { // If 'useDefineForClassFields' is already defined in the users project leave the value as is. // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995 @@ -77,13 +78,32 @@ export function transformCompilerOptions( compilerOptions.compilationMode ??= 'partial'; } - // Enable incremental compilation by default if caching is enabled and incremental is not explicitly disabled - if (compilerOptions.incremental !== false && overrides?.cachePath) { + // Enable incremental compilation by default if caching is enabled and incremental is not explicitly disabled. + // Library compilation does not reuse on-disk .tsbuildinfo files across builds because all JS and .d.ts + // outputs must be emitted into in-memory maps on every cold build, while watch mode uses in-memory + // BuilderProgram state directly. + if (!isLibrary && compilerOptions.incremental !== false && overrides?.cachePath) { compilerOptions.incremental = true; // Set the build info file location to the configured cache directory compilerOptions.tsBuildInfoFile = path.join(overrides.cachePath, '.tsbuildinfo'); } else { compilerOptions.incremental = false; + compilerOptions.tsBuildInfoFile = undefined; + } + + if (isLibrary) { + compilerOptions.target = typeScript.ScriptTarget.ES2022; + compilerOptions.module = typeScript.ModuleKind.ES2022; + compilerOptions.moduleResolution = typeScript.ModuleResolutionKind.Bundler; + compilerOptions.importHelpers = true; + compilerOptions.declaration = true; + compilerOptions.declarationMap = !!( + overrides?.declarationMap ?? compilerOptions.declarationMap + ); + compilerOptions.declarationDir = undefined; + compilerOptions.noEmit = false; + compilerOptions.emitDeclarationOnly = false; + compilerOptions.emitDecoratorMetadata = false; } if ( @@ -112,18 +132,6 @@ export function transformCompilerOptions( }); } - if (isLibrary) { - compilerOptions.target = typeScript.ScriptTarget.ES2022; - compilerOptions.module = typeScript.ModuleKind.ES2022; - compilerOptions.moduleResolution = typeScript.ModuleResolutionKind.Bundler; - compilerOptions.importHelpers = true; - compilerOptions.declaration = true; - compilerOptions.declarationMap = !!( - overrides?.declarationMap ?? compilerOptions.declarationMap - ); - compilerOptions.declarationDir = undefined; - } - // Synchronize custom resolve conditions. // Set if using the supported bundler resolution mode (bundler is the default in new projects) if ( From 0bc182b6cb96d2a5443e4ffa5c94ed4367f5e6bd Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:42:06 +0000 Subject: [PATCH 2/6] fixup! fix(@angular/build): enforce library compilerOptions and skip unused .tsbuildinfo emit --- .../src/builders/library/tests/behavior/build_spec.ts | 4 ++-- .../tools/angular/compilation/angular-compilation_spec.ts | 7 ++++--- .../src/tools/angular/compilation/compiler-options.ts | 8 ++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts index 6c7906210c31..3a4a8acbf1c6 100644 --- a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts +++ b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts @@ -237,7 +237,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => ); }); - it('should succeed without emitting .tsbuildinfo in the persistent cache directory when cache is enabled', async () => { + it('should write and reuse .tsbuildinfo in the persistent cache directory when cache is enabled', async () => { harness.useProject('lib', { root: 'projects/lib', sourceRoot: 'projects/lib/src', @@ -258,7 +258,7 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => expect(firstBuild.result?.success).toBeTrue(); harness .expectFile('.angular/cache/0.0.0-PLACEHOLDER/lib/angular-build-library/.tsbuildinfo') - .toNotExist(); + .toExist(); await harness.writeFile( 'projects/lib/src/public-api.ts', diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts index f253e2140bf6..3b7d790f7102 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import path from 'node:path'; import ts from 'typescript'; import type { AngularHostOptions } from '../angular-host'; import { CompilerOptionOverrides, transformCompilerOptions } from './compiler-options'; @@ -480,7 +481,7 @@ describe('AngularCompilation', () => { expect(compilerOptions.composite).toBe(false); }); - it('enforces library compiler options and skips app-only target warning and .tsbuildinfo', () => { + it('enforces library compiler options and skips app-only target warning', () => { const { compilerOptions, warnings } = transformCompilerOptions( ts, { @@ -502,8 +503,8 @@ describe('AngularCompilation', () => { expect(compilerOptions.noEmit).toBe(false); expect(compilerOptions.emitDeclarationOnly).toBe(false); expect(compilerOptions.emitDecoratorMetadata).toBe(false); - expect(compilerOptions.incremental).toBe(false); - expect(compilerOptions.tsBuildInfoFile).toBeUndefined(); + expect(compilerOptions.incremental).toBe(true); + expect(compilerOptions.tsBuildInfoFile).toBe(path.join('/tmp/cache', '.tsbuildinfo')); expect(warnings).toEqual([]); }); }); diff --git a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts index 22e0cff30bde..497e132ffefd 100644 --- a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts +++ b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts @@ -78,17 +78,13 @@ export function transformCompilerOptions( compilerOptions.compilationMode ??= 'partial'; } - // Enable incremental compilation by default if caching is enabled and incremental is not explicitly disabled. - // Library compilation does not reuse on-disk .tsbuildinfo files across builds because all JS and .d.ts - // outputs must be emitted into in-memory maps on every cold build, while watch mode uses in-memory - // BuilderProgram state directly. - if (!isLibrary && compilerOptions.incremental !== false && overrides?.cachePath) { + // Enable incremental compilation by default if caching is enabled and incremental is not explicitly disabled + if (compilerOptions.incremental !== false && overrides?.cachePath) { compilerOptions.incremental = true; // Set the build info file location to the configured cache directory compilerOptions.tsBuildInfoFile = path.join(overrides.cachePath, '.tsbuildinfo'); } else { compilerOptions.incremental = false; - compilerOptions.tsBuildInfoFile = undefined; } if (isLibrary) { From 546e896675ca2f1b82d71248c1d069688820ce46 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:53:22 +0000 Subject: [PATCH 3/6] fixup! fix(@angular/build): enforce library compilerOptions and skip unused .tsbuildinfo emit --- .../build/src/tools/angular/compilation/compiler-options.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts index 497e132ffefd..9e7ec87ead97 100644 --- a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts +++ b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts @@ -37,7 +37,7 @@ export function transformCompilerOptions( ): { compilerOptions: ng.CompilerOptions; warnings: PartialMessage[] } { const compilerOptions = { ...baseCompilerOptions }; const warnings: PartialMessage[] = []; - const isLibrary = buildType === 'library' || !!baseCompilerOptions.declaration; + const isLibrary = buildType === 'library'; if ( !isLibrary && From 11c6f399c8438c20c272153df951766ab6f4092d Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:03:22 +0000 Subject: [PATCH 4/6] fixup! fix(@angular/build): enforce library compilerOptions and skip unused .tsbuildinfo emit --- .../library/tests/behavior/build_spec.ts | 1 - .../compilation/angular-compilation_spec.ts | 2 -- .../angular/compilation/compiler-options.ts | 33 +++++++++---------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts index 3a4a8acbf1c6..56aa83fbade1 100644 --- a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts +++ b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts @@ -286,7 +286,6 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => ...tsconfig.compilerOptions, noEmit: true, emitDeclarationOnly: true, - emitDecoratorMetadata: true, }; return JSON.stringify(tsconfig, null, 2); diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts index 3b7d790f7102..2fe010d6680f 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts @@ -488,7 +488,6 @@ describe('AngularCompilation', () => { target: ts.ScriptTarget.ES2020, noEmit: true, emitDeclarationOnly: true, - emitDecoratorMetadata: true, isolatedModules: true, }, { @@ -502,7 +501,6 @@ describe('AngularCompilation', () => { expect(compilerOptions.useDefineForClassFields).toBeUndefined(); expect(compilerOptions.noEmit).toBe(false); expect(compilerOptions.emitDeclarationOnly).toBe(false); - expect(compilerOptions.emitDecoratorMetadata).toBe(false); expect(compilerOptions.incremental).toBe(true); expect(compilerOptions.tsBuildInfoFile).toBe(path.join('/tmp/cache', '.tsbuildinfo')); expect(warnings).toEqual([]); diff --git a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts index 9e7ec87ead97..867bb136dabd 100644 --- a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts +++ b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts @@ -39,10 +39,22 @@ export function transformCompilerOptions( const warnings: PartialMessage[] = []; const isLibrary = buildType === 'library'; + if (isLibrary) { + compilerOptions.target = typeScript.ScriptTarget.ES2022; + compilerOptions.module = typeScript.ModuleKind.ES2022; + compilerOptions.importHelpers = true; + compilerOptions.declaration = true; + compilerOptions.declarationMap = !!( + overrides?.declarationMap ?? compilerOptions.declarationMap + ); + compilerOptions.declarationDir = undefined; + compilerOptions.noEmit = false; + compilerOptions.emitDeclarationOnly = false; + } + if ( - !isLibrary && - (compilerOptions.target === undefined || - compilerOptions.target < typeScript.ScriptTarget.ES2022) + compilerOptions.target === undefined || + compilerOptions.target < typeScript.ScriptTarget.ES2022 ) { // If 'useDefineForClassFields' is already defined in the users project leave the value as is. // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995 @@ -87,21 +99,6 @@ export function transformCompilerOptions( compilerOptions.incremental = false; } - if (isLibrary) { - compilerOptions.target = typeScript.ScriptTarget.ES2022; - compilerOptions.module = typeScript.ModuleKind.ES2022; - compilerOptions.moduleResolution = typeScript.ModuleResolutionKind.Bundler; - compilerOptions.importHelpers = true; - compilerOptions.declaration = true; - compilerOptions.declarationMap = !!( - overrides?.declarationMap ?? compilerOptions.declarationMap - ); - compilerOptions.declarationDir = undefined; - compilerOptions.noEmit = false; - compilerOptions.emitDeclarationOnly = false; - compilerOptions.emitDecoratorMetadata = false; - } - if ( compilerOptions.module === undefined || compilerOptions.module < typeScript.ModuleKind.ES2015 From 373dcc196e059a5b0aa8ff0bdc03cdfdc50f3a7d Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:04:10 +0000 Subject: [PATCH 5/6] fixup! fix(@angular/build): enforce library compilerOptions and skip unused .tsbuildinfo emit --- .../library/tests/behavior/build_spec.ts | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts index 56aa83fbade1..0b6de2a26c7f 100644 --- a/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts +++ b/packages/angular/build/src/builders/library/tests/behavior/build_spec.ts @@ -278,31 +278,5 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) => expect(dtsContent).toContain('LibComponent'); expect(dtsContent).toContain('CACHED_BUILD'); }); - - it('should override noEmit and emitDeclarationOnly in tsconfig.lib.json and emit both JS and .d.ts files', async () => { - await harness.modifyFile('projects/lib/tsconfig.lib.json', (content) => { - const tsconfig = JSON.parse(content); - tsconfig.compilerOptions = { - ...tsconfig.compilerOptions, - noEmit: true, - emitDeclarationOnly: true, - }; - - return JSON.stringify(tsconfig, null, 2); - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result } = await harness.executeOnce(); - expect(result?.error).toBeUndefined(); - expect(result?.success).toBeTrue(); - - const fesmContent = harness.readFile('dist/lib/fesm2022/lib.mjs'); - expect(fesmContent).toContain('LibComponent'); - const dtsContent = harness.readFile('dist/lib/types/lib.d.ts'); - expect(dtsContent).toContain('LibComponent'); - }); }); }); From 4f9c4cb96361bf930846939f91c939f893174339 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:24:03 +0000 Subject: [PATCH 6/6] fixup! fix(@angular/build): enforce library compilerOptions and skip unused .tsbuildinfo emit --- .../tools/angular/compilation/angular-compilation_spec.ts | 3 +++ .../build/src/tools/angular/compilation/compiler-options.ts | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts index 2fe010d6680f..842be5528f97 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts @@ -477,7 +477,9 @@ describe('AngularCompilation', () => { expect(compilerOptions['_useTypeScriptTranspilation']).toBe(true); expect(compilerOptions.supportTestBed).toBe(true); expect(compilerOptions.supportJitMode).toBe(true); + expect(compilerOptions.noEmit).toBe(false); expect(compilerOptions.noEmitOnError).toBe(false); + expect(compilerOptions.emitDeclarationOnly).toBe(false); expect(compilerOptions.composite).toBe(false); }); @@ -498,6 +500,7 @@ describe('AngularCompilation', () => { ); expect(compilerOptions.target).toBe(ts.ScriptTarget.ES2022); + expect(compilerOptions.module).toBe(ts.ModuleKind.Preserve); expect(compilerOptions.useDefineForClassFields).toBeUndefined(); expect(compilerOptions.noEmit).toBe(false); expect(compilerOptions.emitDeclarationOnly).toBe(false); diff --git a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts index 867bb136dabd..1c37898abb31 100644 --- a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts +++ b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts @@ -41,15 +41,13 @@ export function transformCompilerOptions( if (isLibrary) { compilerOptions.target = typeScript.ScriptTarget.ES2022; - compilerOptions.module = typeScript.ModuleKind.ES2022; + compilerOptions.module = typeScript.ModuleKind.Preserve; compilerOptions.importHelpers = true; compilerOptions.declaration = true; compilerOptions.declarationMap = !!( overrides?.declarationMap ?? compilerOptions.declarationMap ); compilerOptions.declarationDir = undefined; - compilerOptions.noEmit = false; - compilerOptions.emitDeclarationOnly = false; } if ( @@ -138,7 +136,9 @@ export function transformCompilerOptions( compilerOptions: { ...compilerOptions, basePath: overrides?.basePath ? toPosixPath(overrides.basePath) : compilerOptions.basePath, + noEmit: false, noEmitOnError: false, + emitDeclarationOnly: false, composite: false, inlineSources: !!overrides?.sourcemap, inlineSourceMap: !isLibrary && !!overrides?.sourcemap,