Skip to content

Commit 5b09dbc

Browse files
committed
fix(@angular/build): improve library compilation diagnostics and skip redundant d.ts rebundling
- Forward warnings from `compilationInstance.initialize()` into formatted warnings output. - Run syntactic and option diagnostics (`DiagnosticModes.All & ~DiagnosticModes.Semantic`) when `useTypeChecking` is `false`. - Run `diagnoseFiles` even when `stylesheetErrors.length > 0` so `AotCompilation` drains `affectedFiles` into `diagnosticCache`, and guard `diagnosePromise` against unhandled rejections. - Pass `'css'` for inline `<style>` tags in external `.html` templates in `stylesheetBundler.bundleInline`. - Only clear previous file references for a stylesheet target when stylesheet bundling succeeds. - Avoid marking `.d.ts` files as changed in `changedDtsFiles` when only `.d.ts.map` changed.
1 parent f696eaa commit 5b09dbc

5 files changed

Lines changed: 678 additions & 60 deletions

File tree

‎packages/angular/build/src/builders/library/pipeline/compilation.ts‎

Lines changed: 144 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import { type PartialMessage, formatMessages } from 'esbuild';
1010
import { existsSync } from 'node:fs';
1111
import {
1212
type AngularCompilation,
13+
DiagnosticModes,
14+
type EmitFileResult,
1315
createAngularCompilation,
1416
} from '../../../tools/angular/compilation';
1517
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
1618
import { useTypeChecking } from '../../../utils/environment-options';
19+
import { assertIsError } from '../../../utils/error';
1720
import { toPosixPath } from '../../../utils/path';
1821
import type { BuilderState } from '../builder-state';
1922
import type { LibraryCompilationOutput, NormalizedLibraryOptions } from '../types';
@@ -70,12 +73,22 @@ export async function compileLibrary(
7073

7174
if (effectiveModifiedFiles && effectiveModifiedFiles.size > 0) {
7275
await compilationInstance.update?.(effectiveModifiedFiles);
73-
buildState.clearFileReferences(effectiveModifiedFiles);
7476
}
7577

7678
const stylesheetWarnings: PartialMessage[] = [];
7779
const stylesheetErrors: PartialMessage[] = [];
7880
const failedFiles = new Set<string>();
81+
const clearedReferenceFiles = new Set<string>();
82+
const clearPreviousReferencesOnce = (targetFile: string): void => {
83+
if (!cached) {
84+
return;
85+
}
86+
const posixTarget = toPosixPath(targetFile);
87+
if (!clearedReferenceFiles.has(posixTarget)) {
88+
clearedReferenceFiles.add(posixTarget);
89+
buildState.clearFileReferences([posixTarget]);
90+
}
91+
};
7992

8093
const hostOptions = {
8194
modifiedFiles: effectiveModifiedFiles,
@@ -86,20 +99,24 @@ export async function compileLibrary(
8699
): Promise<string> {
87100
const result = stylesheetFile
88101
? await stylesheetBundler.bundleFile(stylesheetFile)
89-
: await stylesheetBundler.bundleInline(data, containingFile);
90-
91-
if (result.referencedFiles) {
92-
buildState.addFileReferences(containingFile, result.referencedFiles);
93-
if (stylesheetFile) {
94-
buildState.addFileReferences(stylesheetFile, result.referencedFiles);
95-
}
96-
}
102+
: await stylesheetBundler.bundleInline(
103+
data,
104+
containingFile,
105+
containingFile.endsWith('.html') ? 'css' : undefined,
106+
);
97107

98108
if (result.warnings.length > 0) {
99109
stylesheetWarnings.push(...result.warnings);
100110
}
101111

102112
if (result.errors?.length) {
113+
if (result.referencedFiles) {
114+
buildState.addFileReferences(containingFile, result.referencedFiles);
115+
if (stylesheetFile) {
116+
buildState.addFileReferences(stylesheetFile, result.referencedFiles);
117+
}
118+
}
119+
103120
stylesheetErrors.push(...result.errors);
104121
failedFiles.add(toPosixPath(containingFile));
105122
if (stylesheetFile) {
@@ -109,12 +126,28 @@ export async function compileLibrary(
109126
return '';
110127
}
111128

129+
clearPreviousReferencesOnce(containingFile);
130+
if (stylesheetFile) {
131+
clearPreviousReferencesOnce(stylesheetFile);
132+
}
133+
134+
if (result.referencedFiles) {
135+
buildState.addFileReferences(containingFile, result.referencedFiles);
136+
if (stylesheetFile) {
137+
buildState.addFileReferences(stylesheetFile, result.referencedFiles);
138+
}
139+
}
140+
112141
return result.contents;
113142
},
114143
processWebWorker: () => '',
115144
};
116145

117-
const { referencedFiles, compilerOptions } = await compilationInstance.initialize(
146+
const {
147+
referencedFiles,
148+
compilerOptions,
149+
warnings: initWarnings,
150+
} = await compilationInstance.initialize(
118151
tsConfigPath,
119152
hostOptions,
120153
{
@@ -129,6 +162,10 @@ export async function compileLibrary(
129162
'library',
130163
);
131164

165+
if (initWarnings?.length) {
166+
stylesheetWarnings.unshift(...initWarnings);
167+
}
168+
132169
const hasStylesheetErrors = stylesheetErrors.length > 0;
133170
const emittedFiles = hasStylesheetErrors ? [] : await compilationInstance.emitAffectedFiles();
134171
const diagnosePromise = runDiagnosticsAndFormat(
@@ -141,49 +178,14 @@ export async function compileLibrary(
141178

142179
const esmFiles = cached?.esmFiles ?? new Map<string, string>();
143180
const dtsFiles = cached?.dtsFiles ?? new Map<string, string>();
144-
const changedEsmFiles = new Set<string>();
145-
const changedDtsFiles = new Set<string>();
146181
const posixReferencedFiles = new Set(referencedFiles.map(toPosixPath));
147-
148-
if (cached && effectiveModifiedFiles) {
149-
for (const modifiedFile of effectiveModifiedFiles) {
150-
if (posixReferencedFiles.has(modifiedFile)) {
151-
continue;
152-
}
153-
154-
const basePathWithoutExt = modifiedFile.replace(/(?:\.d\.[cm]?ts|\.[cm]?[jt]sx?)$/i, '');
155-
if (basePathWithoutExt === modifiedFile || existsSync(modifiedFile)) {
156-
continue;
157-
}
158-
159-
for (const ext of EMITTED_EXTENSIONS) {
160-
const outputPath = `${basePathWithoutExt}${ext}`;
161-
const mapPath = `${outputPath}.map`;
162-
if (esmFiles.delete(outputPath)) {
163-
changedEsmFiles.add(outputPath);
164-
}
165-
if (dtsFiles.delete(outputPath)) {
166-
changedDtsFiles.add(outputPath);
167-
}
168-
esmFiles.delete(mapPath);
169-
dtsFiles.delete(mapPath);
170-
}
171-
}
172-
}
173-
174-
for (const { filename, contents } of emittedFiles) {
175-
const normalized = toPosixPath(filename);
176-
const isMap = normalized.endsWith('.map');
177-
const isDts = isMap ? isDeclarationSourceMapFile(normalized) : isDeclarationFile(normalized);
178-
const targetFiles = isDts ? dtsFiles : esmFiles;
179-
180-
if (targetFiles.get(normalized) !== contents) {
181-
targetFiles.set(normalized, contents);
182-
(isDts ? changedDtsFiles : changedEsmFiles).add(
183-
isMap ? normalized.slice(0, -4) : normalized,
184-
);
185-
}
186-
}
182+
const { changedEsmFiles, changedDtsFiles } = updateEmittedFileCaches(
183+
emittedFiles,
184+
esmFiles,
185+
dtsFiles,
186+
posixReferencedFiles,
187+
cached ? effectiveModifiedFiles : undefined,
188+
);
187189

188190
return {
189191
esmFiles,
@@ -210,13 +212,79 @@ export async function compileLibrary(
210212
}
211213
}
212214

215+
/**
216+
* Updates the in-memory ESM and DTS file maps with newly emitted files and prunes deleted
217+
* source files, returning the sets of changed ESM and DTS file paths.
218+
*
219+
* @param emittedFiles Newly emitted files from the Angular compilation.
220+
* @param esmFiles In-memory map of POSIX ESM file paths to contents.
221+
* @param dtsFiles In-memory map of POSIX declaration file paths to contents.
222+
* @param posixReferencedFiles Set of POSIX file paths currently referenced by the program.
223+
* @param modifiedFiles Optional set of modified POSIX file paths in incremental builds.
224+
* @returns Sets of changed ESM and DTS file paths.
225+
*/
226+
function updateEmittedFileCaches(
227+
emittedFiles: Iterable<EmitFileResult>,
228+
esmFiles: Map<string, string>,
229+
dtsFiles: Map<string, string>,
230+
posixReferencedFiles: ReadonlySet<string>,
231+
modifiedFiles?: ReadonlySet<string>,
232+
): { changedEsmFiles: Set<string>; changedDtsFiles: Set<string> } {
233+
const changedEsmFiles = new Set<string>();
234+
const changedDtsFiles = new Set<string>();
235+
236+
if (modifiedFiles) {
237+
for (const modifiedFile of modifiedFiles) {
238+
if (posixReferencedFiles.has(modifiedFile)) {
239+
continue;
240+
}
241+
242+
const basePathWithoutExt = modifiedFile.replace(/(?:\.d\.[cm]?ts|\.[cm]?[jt]sx?)$/i, '');
243+
if (basePathWithoutExt === modifiedFile || existsSync(modifiedFile)) {
244+
continue;
245+
}
246+
247+
for (const ext of EMITTED_EXTENSIONS) {
248+
const outputPath = `${basePathWithoutExt}${ext}`;
249+
const mapPath = `${outputPath}.map`;
250+
if (esmFiles.delete(outputPath)) {
251+
changedEsmFiles.add(outputPath);
252+
}
253+
if (dtsFiles.delete(outputPath)) {
254+
changedDtsFiles.add(outputPath);
255+
}
256+
esmFiles.delete(mapPath);
257+
dtsFiles.delete(mapPath);
258+
}
259+
}
260+
}
261+
262+
for (const { filename, contents } of emittedFiles) {
263+
const normalized = toPosixPath(filename);
264+
const isMap = normalized.endsWith('.map');
265+
const isDts = isMap ? isDeclarationSourceMapFile(normalized) : isDeclarationFile(normalized);
266+
const targetFiles = isDts ? dtsFiles : esmFiles;
267+
268+
if (targetFiles.get(normalized) !== contents) {
269+
targetFiles.set(normalized, contents);
270+
if (!isDts) {
271+
changedEsmFiles.add(isMap ? normalized.slice(0, -4) : normalized);
272+
} else if (!isMap) {
273+
changedDtsFiles.add(normalized);
274+
}
275+
}
276+
}
277+
278+
return { changedEsmFiles, changedDtsFiles };
279+
}
280+
213281
/**
214282
* Formats stylesheet bundling diagnostics and TypeScript/Angular diagnostics
215283
* and records them on the build state's execution result.
216284
*
217285
* @param compilationInstance The active Angular compilation instance.
218286
* @param stylesheetErrors List of stylesheet bundling errors.
219-
* @param stylesheetWarnings List of stylesheet bundling warnings.
287+
* @param stylesheetWarnings List of initialization and stylesheet bundling warnings.
220288
* @param colors Whether diagnostic messages should be formatted with ANSI colors.
221289
* @param buildState The build state whose execution result receives the formatted diagnostics.
222290
*/
@@ -231,23 +299,39 @@ async function runDiagnosticsAndFormat(
231299
const errors = [...stylesheetErrors];
232300
const warnings: PartialMessage[] = [];
233301

234-
if (errors.length === 0 && useTypeChecking) {
235-
const diagnostics = await compilationInstance.diagnoseFiles();
302+
try {
303+
const diagnostics = await compilationInstance.diagnoseFiles(
304+
useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic,
305+
);
236306
if (diagnostics.errors?.length) {
237307
errors.push(...diagnostics.errors);
238308
}
239309
if (diagnostics.warnings?.length) {
240310
warnings.push(...diagnostics.warnings);
241311
}
312+
} catch (error) {
313+
assertIsError(error);
314+
errors.push({
315+
text: 'Angular compilation diagnostics failed.',
316+
location: null,
317+
notes: [{ text: error.stack ?? error.message, location: null }],
318+
});
242319
}
243320

244321
warnings.push(...stylesheetWarnings);
245322

246-
if (errors.length > 0) {
247-
executionResult.addErrors(await formatMessages(errors, { kind: 'error', color: colors }));
248-
}
323+
try {
324+
if (errors.length > 0) {
325+
executionResult.addErrors(await formatMessages(errors, { kind: 'error', color: colors }));
326+
}
249327

250-
if (warnings.length > 0) {
251-
executionResult.addWarnings(await formatMessages(warnings, { kind: 'warning', color: colors }));
328+
if (warnings.length > 0) {
329+
executionResult.addWarnings(
330+
await formatMessages(warnings, { kind: 'warning', color: colors }),
331+
);
332+
}
333+
} catch (error) {
334+
assertIsError(error);
335+
executionResult.addError(error.message);
252336
}
253337
}

0 commit comments

Comments
 (0)