Skip to content

Commit 295550b

Browse files
authored
refactor(@angular/build): support library compilation in AngularCompilation
1 parent eb2ba36 commit 295550b

7 files changed

Lines changed: 62 additions & 21 deletions

File tree

‎packages/angular/build/src/tools/angular/compilation/angular-compilation.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface FileTransformResult {
2323

2424
export interface AngularCompilationOptions {
2525
allowJs?: boolean;
26+
declarationMap?: boolean;
2627
isolatedModules?: boolean;
2728
sourceMap?: boolean;
2829
inlineSourceMap?: boolean;
@@ -52,6 +53,7 @@ export abstract class AngularCompilation {
5253
tsconfig: string,
5354
hostOptions: AngularHostOptions,
5455
compilerOptionOverrides?: CompilerOptionOverrides,
56+
buildType?: 'application' | 'library',
5557
): Promise<AngularCompilationResult>;
5658

5759
emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>> {

‎packages/angular/build/src/tools/angular/compilation/aot-compilation.ts‎

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ export class AotCompilation extends TypeScriptCompilation {
6161
super();
6262
}
6363

64+
// eslint-disable-next-line max-lines-per-function
6465
async initialize(
6566
tsconfig: string,
6667
hostOptions: AngularHostOptions,
6768
compilerOptionOverrides?: CompilerOptionOverrides,
69+
buildType: 'application' | 'library' = 'application',
6870
): Promise<AngularCompilationResult> {
6971
// Dynamically load the Angular compiler CLI package
7072
const { NgtscProgram, OptimizeFor } = await TypeScriptCompilation.loadCompilerCli();
@@ -75,7 +77,7 @@ export class AotCompilation extends TypeScriptCompilation {
7577
rootNames,
7678
errors: configurationDiagnostics,
7779
warnings,
78-
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides);
80+
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);
7981

8082
const useTypeScriptTranspilation =
8183
(compilerOptions['_useTypeScriptTranspilation'] as boolean | undefined) ??
@@ -332,9 +334,11 @@ export class AotCompilation extends TypeScriptCompilation {
332334
useTypeScriptTranspilation,
333335
} = this.#state;
334336
const compilerOptions = typeScriptProgram.getCompilerOptions();
337+
const isLibraryEmit = !!compilerOptions.declaration;
335338
const buildInfoFilename = compilerOptions.tsBuildInfoFile ?? '.tsbuildinfo';
336339

337-
const emittedFiles = new Map<ts.SourceFile, EmitFileResult>();
340+
const emittedFiles = new Map<string, EmitFileResult>();
341+
const emittedSourceFiles = new Set<ts.SourceFile>();
338342
const writeFileCallback: ts.WriteFileCallback = (filename, contents, _a, _b, sourceFiles) => {
339343
if (!sourceFiles?.length && filename.endsWith(buildInfoFilename)) {
340344
// Save builder info contents to specified location
@@ -350,17 +354,21 @@ export class AotCompilation extends TypeScriptCompilation {
350354
}
351355

352356
angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
353-
emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents });
357+
emittedSourceFiles.add(sourceFile);
358+
const targetFilename = isLibraryEmit ? filename : sourceFile.fileName;
359+
emittedFiles.set(targetFilename, { filename: targetFilename, contents });
354360
};
355361
const transformers = angularCompiler.prepareEmit().transformers;
356-
transformers.before ??= [];
357-
transformers.before.push(
358-
replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()),
359-
webWorkerTransform,
360-
);
362+
if (!isLibraryEmit) {
363+
transformers.before ??= [];
364+
transformers.before.push(
365+
replaceBootstrap(() => typeScriptProgram.getProgram().getTypeChecker()),
366+
webWorkerTransform,
367+
);
361368

362-
if (!this.browserOnlyBuild) {
363-
transformers.before.push(lazyRoutesTransformer(compilerOptions, compilerHost));
369+
if (!this.browserOnlyBuild) {
370+
transformers.before.push(lazyRoutesTransformer(compilerOptions, compilerHost));
371+
}
364372
}
365373

366374
// Emit is handled in write file callback when using TypeScript
@@ -394,7 +402,7 @@ export class AotCompilation extends TypeScriptCompilation {
394402

395403
// Angular may have files that must be emitted but TypeScript does not consider affected
396404
for (const sourceFile of typeScriptProgram.getSourceFiles()) {
397-
if (emittedFiles.has(sourceFile) || angularCompiler.ignoreForEmit.has(sourceFile)) {
405+
if (emittedSourceFiles.has(sourceFile) || angularCompiler.ignoreForEmit.has(sourceFile)) {
398406
continue;
399407
}
400408

@@ -410,7 +418,8 @@ export class AotCompilation extends TypeScriptCompilation {
410418
}
411419

412420
if (useTypeScriptTranspilation) {
413-
typeScriptProgram.emit(sourceFile, writeFileCallback, undefined, undefined, transformers);
421+
const emitOnly = affectedFiles.has(sourceFile) ? undefined : false;
422+
typeScriptProgram.emit(sourceFile, writeFileCallback, undefined, emitOnly, transformers);
414423
continue;
415424
}
416425

@@ -451,13 +460,14 @@ export class AotCompilation extends TypeScriptCompilation {
451460
contents += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`;
452461
} else if (compilerOptions.sourceMap) {
453462
const mapFilename = sourceFile.fileName + '.map';
454-
emittedFiles.set(sourceFile, { filename: mapFilename, contents: printResult.map });
463+
emittedFiles.set(mapFilename, { filename: mapFilename, contents: printResult.map });
455464
}
456465
}
457466
}
458467

459468
angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
460-
emittedFiles.set(sourceFile, { filename: sourceFile.fileName, contents });
469+
emittedSourceFiles.add(sourceFile);
470+
emittedFiles.set(sourceFile.fileName, { filename: sourceFile.fileName, contents });
461471
}
462472

463473
return emittedFiles.values();

‎packages/angular/build/src/tools/angular/compilation/compiler-options.ts‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ export interface CompilerOptionOverrides {
2121
includeTestMetadata?: boolean;
2222
customConditions?: string[];
2323
rootFiles?: string[];
24+
declarationMap?: boolean;
25+
compilationMode?: 'full' | 'partial';
26+
paths?: Record<string, string[]>;
2427
}
2528

2629
export function transformCompilerOptions(
2730
typeScript: typeof ts,
2831
baseCompilerOptions: ng.CompilerOptions,
2932
overrides?: CompilerOptionOverrides,
3033
tsconfig?: string,
34+
buildType: 'application' | 'library' = 'application',
3135
): { compilerOptions: ng.CompilerOptions; warnings: PartialMessage[] } {
3236
const compilerOptions = { ...baseCompilerOptions };
3337
const warnings: PartialMessage[] = [];
38+
const isLibrary = buildType === 'library';
3439

3540
if (
3641
compilerOptions.target === undefined ||
@@ -57,13 +62,15 @@ export function transformCompilerOptions(
5762
});
5863
}
5964

60-
if (compilerOptions.compilationMode === 'partial') {
65+
if (!isLibrary && compilerOptions.compilationMode === 'partial') {
6166
warnings.push({
6267
text: 'Angular partial compilation mode is not supported when building applications.',
6368
location: null,
6469
notes: [{ text: 'Full compilation mode will be used instead.' }],
6570
});
6671
compilerOptions.compilationMode = 'full';
72+
} else if (overrides?.compilationMode) {
73+
compilerOptions.compilationMode = overrides.compilationMode;
6774
}
6875

6976
// Enable incremental compilation by default if caching is enabled and incremental is not explicitly disabled
@@ -101,6 +108,16 @@ export function transformCompilerOptions(
101108
});
102109
}
103110

111+
if (isLibrary) {
112+
compilerOptions.target = typeScript.ScriptTarget.ES2022;
113+
compilerOptions.module = typeScript.ModuleKind.ES2022;
114+
compilerOptions.moduleResolution = typeScript.ModuleResolutionKind.Bundler;
115+
compilerOptions.importHelpers = true;
116+
compilerOptions.declaration = true;
117+
compilerOptions.declarationMap = overrides?.declarationMap;
118+
compilerOptions.declarationDir = undefined;
119+
}
120+
104121
// Synchronize custom resolve conditions.
105122
// Set if using the supported bundler resolution mode (bundler is the default in new projects)
106123
if (
@@ -116,21 +133,25 @@ export function transformCompilerOptions(
116133
noEmitOnError: false,
117134
composite: false,
118135
inlineSources: !!overrides?.sourcemap,
119-
inlineSourceMap: !!overrides?.sourcemap,
120-
sourceMap: undefined,
136+
inlineSourceMap: !isLibrary && !!overrides?.sourcemap,
137+
sourceMap: isLibrary ? !!overrides?.sourcemap : undefined,
121138
mapRoot: undefined,
122139
sourceRoot: undefined,
123140
preserveSymlinks: overrides?.preserveSymlinks,
124141
externalRuntimeStyles: overrides?.externalRuntimeStyles,
125142
_enableHmr: !!overrides?.enableHmr,
126143
// TypeScript transpilation is forced if:
144+
// - Building a library (TypeScript emits both .js and .d.ts in a single pass).
127145
// - isolatedModules is disabled (TS needs full module types to emit JS).
128146
// - Karma code coverage is active (the coverage instrumentation transformer is Babel-based
129147
// and cannot parse raw TypeScript code; Vitest handles coverage instrumentation downstream).
130148
_useTypeScriptTranspilation:
131-
!compilerOptions.isolatedModules || !!overrides?.instrumentForCoverage,
132-
supportTestBed: !!overrides?.includeTestMetadata,
133-
supportJitMode: !!overrides?.includeTestMetadata,
149+
isLibrary || !compilerOptions.isolatedModules || !!overrides?.instrumentForCoverage,
150+
supportTestBed: isLibrary ? undefined : !!overrides?.includeTestMetadata,
151+
supportJitMode: isLibrary ? undefined : !!overrides?.includeTestMetadata,
152+
paths: overrides?.paths
153+
? { ...baseCompilerOptions.paths, ...overrides.paths }
154+
: baseCompilerOptions.paths,
134155
},
135156
warnings,
136157
};

‎packages/angular/build/src/tools/angular/compilation/jit-compilation.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export class JitCompilation extends TypeScriptCompilation {
4343
tsconfig: string,
4444
hostOptions: AngularHostOptions,
4545
compilerOptionOverrides?: CompilerOptionOverrides,
46+
buildType: 'application' | 'library' = 'application',
4647
): Promise<AngularCompilationResult> {
4748
// Dynamically load the Angular compiler CLI package
4849
const { constructorParametersDownlevelTransform } =
@@ -54,7 +55,7 @@ export class JitCompilation extends TypeScriptCompilation {
5455
rootNames,
5556
errors: configurationDiagnostics,
5657
warnings,
57-
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides);
58+
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);
5859

5960
if (hostOptions.modifiedFiles) {
6061
this.invalidateFiles(hostOptions.modifiedFiles);

‎packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class ParallelCompilation extends AngularCompilation {
5252
tsconfig: string,
5353
hostOptions: AngularHostOptions,
5454
compilerOptionOverrides?: CompilerOptionOverrides,
55+
buildType: 'application' | 'library' = 'application',
5556
): Promise<AngularCompilationResult> {
5657
const stylesheetChannel = new MessageChannel();
5758
// The request identifier is required because Angular can issue multiple concurrent requests
@@ -94,6 +95,7 @@ export class ParallelCompilation extends AngularCompilation {
9495
jit: this.jit,
9596
browserOnlyBuild: this.browserOnlyBuild,
9697
compilerOptionOverrides,
98+
buildType,
9799
stylesheetPort: stylesheetChannel.port2,
98100
webWorkerPort: webWorkerChannel.port2,
99101
webWorkerSignal,

‎packages/angular/build/src/tools/angular/compilation/parallel-worker.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface InitRequest {
2727
tsconfig: string;
2828
fileReplacements?: Record<string, string>;
2929
compilerOptionOverrides?: CompilerOptionOverrides;
30+
buildType?: 'application' | 'library';
3031
stylesheetPort: MessagePort;
3132
webWorkerPort: MessagePort;
3233
webWorkerSignal: Int32Array;
@@ -111,6 +112,7 @@ export async function initialize(request: InitRequest): Promise<AngularCompilati
111112
},
112113
},
113114
request.compilerOptionOverrides,
115+
request.buildType,
114116
);
115117

116118
success = true;
@@ -123,6 +125,7 @@ export async function initialize(request: InitRequest): Promise<AngularCompilati
123125
// TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently.
124126
compilerOptions: {
125127
allowJs: compilerOptions.allowJs,
128+
declarationMap: compilerOptions.declarationMap,
126129
isolatedModules: compilerOptions.isolatedModules,
127130
sourceMap: compilerOptions.sourceMap,
128131
inlineSourceMap: compilerOptions.inlineSourceMap,

‎packages/angular/build/src/tools/angular/compilation/typescript-compilation.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
3434
protected async loadConfiguration(
3535
tsconfig: string,
3636
compilerOptionOverrides?: CompilerOptionOverrides,
37+
buildType: 'application' | 'library' = 'application',
3738
): Promise<TransformedConfiguration> {
3839
const { readConfiguration } = await TypeScriptCompilation.loadCompilerCli();
3940

@@ -78,6 +79,7 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
7879
originalCompilerOptions,
7980
compilerOptionOverrides,
8081
tsconfig,
82+
buildType,
8183
);
8284

8385
return {

0 commit comments

Comments
 (0)