Skip to content

Commit de9eae8

Browse files
committed
fix(@angular/build): improve library compilation diagnostics and stylesheet reference tracking
- 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.
1 parent 193abc0 commit de9eae8

5 files changed

Lines changed: 661 additions & 57 deletions

File tree

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

Lines changed: 133 additions & 57 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,17 +99,23 @@ 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(stylesheetFile ?? containingFile, result.referencedFiles);
93-
}
102+
: await stylesheetBundler.bundleInline(
103+
data,
104+
containingFile,
105+
containingFile.endsWith('.html') ? 'css' : undefined,
106+
);
94107

95108
if (result.warnings.length > 0) {
96109
stylesheetWarnings.push(...result.warnings);
97110
}
98111

112+
const referenceTarget = stylesheetFile ?? containingFile;
113+
99114
if (result.errors?.length) {
115+
if (result.referencedFiles) {
116+
buildState.addFileReferences(referenceTarget, result.referencedFiles);
117+
}
118+
100119
stylesheetErrors.push(...result.errors);
101120
failedFiles.add(toPosixPath(containingFile));
102121
if (stylesheetFile) {
@@ -106,12 +125,22 @@ export async function compileLibrary(
106125
return '';
107126
}
108127

128+
clearPreviousReferencesOnce(referenceTarget);
129+
130+
if (result.referencedFiles) {
131+
buildState.addFileReferences(referenceTarget, result.referencedFiles);
132+
}
133+
109134
return result.contents;
110135
},
111136
processWebWorker: () => '',
112137
};
113138

114-
const { referencedFiles, compilerOptions } = await compilationInstance.initialize(
139+
const {
140+
referencedFiles,
141+
compilerOptions,
142+
warnings: initWarnings,
143+
} = await compilationInstance.initialize(
115144
tsConfigPath,
116145
hostOptions,
117146
{
@@ -126,6 +155,10 @@ export async function compileLibrary(
126155
'library',
127156
);
128157

158+
if (initWarnings?.length) {
159+
stylesheetWarnings.unshift(...initWarnings);
160+
}
161+
129162
const hasStylesheetErrors = stylesheetErrors.length > 0;
130163
const emittedFiles = hasStylesheetErrors ? [] : await compilationInstance.emitAffectedFiles();
131164
const diagnosePromise = runDiagnosticsAndFormat(
@@ -138,49 +171,14 @@ export async function compileLibrary(
138171

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

185183
return {
186184
esmFiles,
@@ -207,13 +205,75 @@ export async function compileLibrary(
207205
}
208206
}
209207

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

231-
if (errors.length === 0 && useTypeChecking) {
232-
const diagnostics = await compilationInstance.diagnoseFiles();
291+
try {
292+
const diagnostics = await compilationInstance.diagnoseFiles(
293+
useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic,
294+
);
233295
if (diagnostics.errors?.length) {
234296
errors.push(...diagnostics.errors);
235297
}
236298
if (diagnostics.warnings?.length) {
237299
warnings.push(...diagnostics.warnings);
238300
}
301+
} catch (error) {
302+
assertIsError(error);
303+
errors.push({
304+
text: 'Angular compilation diagnostics failed.',
305+
location: null,
306+
notes: [{ text: error.stack ?? error.message, location: null }],
307+
});
239308
}
240309

241310
warnings.push(...stylesheetWarnings);
242311

243-
if (errors.length > 0) {
244-
executionResult.addErrors(await formatMessages(errors, { kind: 'error', color: colors }));
245-
}
312+
try {
313+
if (errors.length > 0) {
314+
executionResult.addErrors(await formatMessages(errors, { kind: 'error', color: colors }));
315+
}
246316

247-
if (warnings.length > 0) {
248-
executionResult.addWarnings(await formatMessages(warnings, { kind: 'warning', color: colors }));
317+
if (warnings.length > 0) {
318+
executionResult.addWarnings(
319+
await formatMessages(warnings, { kind: 'warning', color: colors }),
320+
);
321+
}
322+
} catch (error) {
323+
assertIsError(error);
324+
executionResult.addError(error.message);
249325
}
250326
}

0 commit comments

Comments
 (0)