diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts index 6bd836139c5f..d766680d1674 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts @@ -23,6 +23,7 @@ export interface FileTransformResult { export interface AngularCompilationOptions { allowJs?: boolean; + declarationMap?: boolean; isolatedModules?: boolean; sourceMap?: boolean; inlineSourceMap?: boolean; @@ -52,6 +53,7 @@ export abstract class AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionOverrides?: CompilerOptionOverrides, + buildType?: 'application' | 'library', ): Promise; emitAffectedFiles(): Iterable | Promise> { diff --git a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts index b42b1273592d..e4047a3e3e1b 100644 --- a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts @@ -61,10 +61,12 @@ export class AotCompilation extends TypeScriptCompilation { super(); } + // eslint-disable-next-line max-lines-per-function async initialize( tsconfig: string, hostOptions: AngularHostOptions, compilerOptionOverrides?: CompilerOptionOverrides, + buildType: 'application' | 'library' = 'application', ): Promise { // Dynamically load the Angular compiler CLI package const { NgtscProgram, OptimizeFor } = await TypeScriptCompilation.loadCompilerCli(); @@ -75,7 +77,7 @@ export class AotCompilation extends TypeScriptCompilation { rootNames, errors: configurationDiagnostics, warnings, - } = await this.loadConfiguration(tsconfig, compilerOptionOverrides); + } = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType); const useTypeScriptTranspilation = (compilerOptions['_useTypeScriptTranspilation'] as boolean | undefined) ?? @@ -332,9 +334,11 @@ export class AotCompilation extends TypeScriptCompilation { useTypeScriptTranspilation, } = this.#state; const compilerOptions = typeScriptProgram.getCompilerOptions(); + const isLibraryEmit = !!compilerOptions.declaration; const buildInfoFilename = compilerOptions.tsBuildInfoFile ?? '.tsbuildinfo'; - const emittedFiles = new Map(); + const emittedFiles = new Map(); + const emittedSourceFiles = new Set(); const writeFileCallback: ts.WriteFileCallback = (filename, contents, _a, _b, sourceFiles) => { if (!sourceFiles?.length && filename.endsWith(buildInfoFilename)) { // Save builder info contents to specified location @@ -350,17 +354,21 @@ export class AotCompilation extends TypeScriptCompilation { } angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile); - emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents }); + emittedSourceFiles.add(sourceFile); + const targetFilename = isLibraryEmit ? filename : sourceFile.fileName; + emittedFiles.set(targetFilename, { filename: targetFilename, contents }); }; const transformers = angularCompiler.prepareEmit().transformers; - transformers.before ??= []; - transformers.before.push( - replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()), - webWorkerTransform, - ); + if (!isLibraryEmit) { + transformers.before ??= []; + transformers.before.push( + replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()), + webWorkerTransform, + ); - if (!this.browserOnlyBuild) { - transformers.before.push(lazyRoutesTransformer(compilerOptions, compilerHost)); + if (!this.browserOnlyBuild) { + transformers.before.push(lazyRoutesTransformer(compilerOptions, compilerHost)); + } } // Emit is handled in write file callback when using TypeScript @@ -394,7 +402,7 @@ export class AotCompilation extends TypeScriptCompilation { // Angular may have files that must be emitted but TypeScript does not consider affected for (const sourceFile of typeScriptProgram.getSourceFiles()) { - if (emittedFiles.has(sourceFile) || angularCompiler.ignoreForEmit.has(sourceFile)) { + if (emittedSourceFiles.has(sourceFile) || angularCompiler.ignoreForEmit.has(sourceFile)) { continue; } @@ -410,7 +418,8 @@ export class AotCompilation extends TypeScriptCompilation { } if (useTypeScriptTranspilation) { - typeScriptProgram.emit(sourceFile, writeFileCallback, undefined, undefined, transformers); + const emitOnly = affectedFiles.has(sourceFile) ? undefined : false; + typeScriptProgram.emit(sourceFile, writeFileCallback, undefined, emitOnly, transformers); continue; } @@ -451,13 +460,14 @@ export class AotCompilation extends TypeScriptCompilation { contents += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`; } else if (compilerOptions.sourceMap) { const mapFilename = sourceFile.fileName + '.map'; - emittedFiles.set(sourceFile, { filename: mapFilename, contents: printResult.map }); + emittedFiles.set(mapFilename, { filename: mapFilename, contents: printResult.map }); } } } angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile); - emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents }); + emittedSourceFiles.add(sourceFile); + emittedFiles.set(sourceFile.fileName, { filename: sourceFile.fileName, contents }); } return emittedFiles.values(); 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 45af80eae6d3..1720a8bb0516 100644 --- a/packages/angular/build/src/tools/angular/compilation/compiler-options.ts +++ b/packages/angular/build/src/tools/angular/compilation/compiler-options.ts @@ -21,6 +21,9 @@ export interface CompilerOptionOverrides { includeTestMetadata?: boolean; customConditions?: string[]; rootFiles?: string[]; + declarationMap?: boolean; + compilationMode?: 'full' | 'partial'; + paths?: Record; } export function transformCompilerOptions( @@ -28,9 +31,11 @@ export function transformCompilerOptions( baseCompilerOptions: ng.CompilerOptions, overrides?: CompilerOptionOverrides, tsconfig?: string, + buildType: 'application' | 'library' = 'application', ): { compilerOptions: ng.CompilerOptions; warnings: PartialMessage[] } { const compilerOptions = { ...baseCompilerOptions }; const warnings: PartialMessage[] = []; + const isLibrary = buildType === 'library'; if ( compilerOptions.target === undefined || @@ -57,13 +62,15 @@ export function transformCompilerOptions( }); } - if (compilerOptions.compilationMode === 'partial') { + if (!isLibrary && compilerOptions.compilationMode === 'partial') { warnings.push({ text: 'Angular partial compilation mode is not supported when building applications.', location: null, notes: [{ text: 'Full compilation mode will be used instead.' }], }); compilerOptions.compilationMode = 'full'; + } else if (overrides?.compilationMode) { + compilerOptions.compilationMode = overrides.compilationMode; } // Enable incremental compilation by default if caching is enabled and incremental is not explicitly disabled @@ -101,6 +108,16 @@ 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.declarationDir = undefined; + } + // Synchronize custom resolve conditions. // Set if using the supported bundler resolution mode (bundler is the default in new projects) if ( @@ -116,21 +133,25 @@ export function transformCompilerOptions( noEmitOnError: false, composite: false, inlineSources: !!overrides?.sourcemap, - inlineSourceMap: !!overrides?.sourcemap, - sourceMap: undefined, + inlineSourceMap: !isLibrary && !!overrides?.sourcemap, + sourceMap: isLibrary ? !!overrides?.sourcemap : undefined, mapRoot: undefined, sourceRoot: undefined, preserveSymlinks: overrides?.preserveSymlinks, externalRuntimeStyles: overrides?.externalRuntimeStyles, _enableHmr: !!overrides?.enableHmr, // TypeScript transpilation is forced if: + // - Building a library (TypeScript emits both .js and .d.ts in a single pass). // - isolatedModules is disabled (TS needs full module types to emit JS). // - Karma code coverage is active (the coverage instrumentation transformer is Babel-based // and cannot parse raw TypeScript code; Vitest handles coverage instrumentation downstream). _useTypeScriptTranspilation: - !compilerOptions.isolatedModules || !!overrides?.instrumentForCoverage, - supportTestBed: !!overrides?.includeTestMetadata, - supportJitMode: !!overrides?.includeTestMetadata, + isLibrary || !compilerOptions.isolatedModules || !!overrides?.instrumentForCoverage, + supportTestBed: isLibrary ? undefined : !!overrides?.includeTestMetadata, + supportJitMode: isLibrary ? undefined : !!overrides?.includeTestMetadata, + paths: overrides?.paths + ? { ...baseCompilerOptions.paths, ...overrides.paths } + : baseCompilerOptions.paths, }, warnings, }; diff --git a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts index e89db1e75aa4..773279e236a9 100644 --- a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts @@ -43,6 +43,7 @@ export class JitCompilation extends TypeScriptCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionOverrides?: CompilerOptionOverrides, + buildType: 'application' | 'library' = 'application', ): Promise { // Dynamically load the Angular compiler CLI package const { constructorParametersDownlevelTransform } = @@ -54,7 +55,7 @@ export class JitCompilation extends TypeScriptCompilation { rootNames, errors: configurationDiagnostics, warnings, - } = await this.loadConfiguration(tsconfig, compilerOptionOverrides); + } = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType); if (hostOptions.modifiedFiles) { this.invalidateFiles(hostOptions.modifiedFiles); diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts index 0b58423a418b..6e4debf62501 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts @@ -52,6 +52,7 @@ export class ParallelCompilation extends AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionOverrides?: CompilerOptionOverrides, + buildType: 'application' | 'library' = 'application', ): Promise { const stylesheetChannel = new MessageChannel(); // The request identifier is required because Angular can issue multiple concurrent requests @@ -94,6 +95,7 @@ export class ParallelCompilation extends AngularCompilation { jit: this.jit, browserOnlyBuild: this.browserOnlyBuild, compilerOptionOverrides, + buildType, stylesheetPort: stylesheetChannel.port2, webWorkerPort: webWorkerChannel.port2, webWorkerSignal, diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts index 5bf293da727d..a74c032a1b70 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts @@ -27,6 +27,7 @@ export interface InitRequest { tsconfig: string; fileReplacements?: Record; compilerOptionOverrides?: CompilerOptionOverrides; + buildType?: 'application' | 'library'; stylesheetPort: MessagePort; webWorkerPort: MessagePort; webWorkerSignal: Int32Array; @@ -111,6 +112,7 @@ export async function initialize(request: InitRequest): Promise { const { readConfiguration } = await TypeScriptCompilation.loadCompilerCli(); @@ -78,6 +79,7 @@ export abstract class TypeScriptCompilation extends AngularCompilation { originalCompilerOptions, compilerOptionOverrides, tsconfig, + buildType, ); return {