Skip to content

Commit 0581c45

Browse files
committed
refactor(@angular/build): support application incremental build result in dev-server
The dev-server will now leverage the incremental build result data from the application builder. This removes the need to directly analyze all the newly built files within the dev-server to determine what type of update is needed. Incremental build results also only contain the files that are new and/or modified and removes the need to pass a potentially large amount of file content between the application build and the dev-server.
1 parent ddae37f commit 0581c45

File tree

1 file changed

+92
-76
lines changed

1 file changed

+92
-76
lines changed

packages/angular/build/src/builders/dev-server/vite-server.ts

Lines changed: 92 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export async function* serveWithVite(
147147
browserOptions.templateUpdates =
148148
serverOptions.liveReload && serverOptions.hmr && useComponentTemplateHmr;
149149

150+
browserOptions.incrementalResults = true;
151+
150152
// Setup the prebundling transformer that will be shared across Vite prebundling requests
151153
const prebundleTransformer = new JavaScriptTransformer(
152154
// Always enable JIT linking to support applications built with and without AOT.
@@ -225,10 +227,25 @@ export async function* serveWithVite(
225227
}
226228

227229
assetFiles.clear();
228-
for (const [outputPath, file] of Object.entries(result.files)) {
230+
componentStyles.clear();
231+
generatedFiles.clear();
232+
for (const entry of Object.entries(result.files)) {
233+
const [outputPath, file] = entry;
229234
if (file.origin === 'disk') {
230235
assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
236+
continue;
231237
}
238+
239+
updateResultRecord(
240+
outputPath,
241+
file,
242+
normalizePath,
243+
htmlIndexPath,
244+
generatedFiles,
245+
componentStyles,
246+
// The initial build will not yet have a server setup
247+
!server,
248+
);
232249
}
233250

234251
// Invalidate SSR module graph to ensure that only new rebuild is used and not stale component updates
@@ -239,18 +256,36 @@ export async function* serveWithVite(
239256
// Clear stale template updates on code rebuilds
240257
templateUpdates.clear();
241258

242-
// Analyze result files for changes
243-
analyzeResultFiles(
244-
normalizePath,
245-
htmlIndexPath,
246-
result.files,
247-
generatedFiles,
248-
componentStyles,
249-
);
250259
break;
251260
case ResultKind.Incremental:
252261
assert(server, 'Builder must provide an initial full build before incremental results.');
253-
// TODO: Implement support -- application builder currently does not use
262+
263+
for (const removed of result.removed) {
264+
const filePath = '/' + normalizePath(removed.path);
265+
generatedFiles.delete(filePath);
266+
assetFiles.delete(filePath);
267+
}
268+
for (const modified of result.modified) {
269+
updateResultRecord(
270+
modified,
271+
result.files[modified],
272+
normalizePath,
273+
htmlIndexPath,
274+
generatedFiles,
275+
componentStyles,
276+
);
277+
}
278+
for (const added of result.added) {
279+
updateResultRecord(
280+
added,
281+
result.files[added],
282+
normalizePath,
283+
htmlIndexPath,
284+
generatedFiles,
285+
componentStyles,
286+
);
287+
}
288+
254289
break;
255290
case ResultKind.ComponentUpdate:
256291
assert(serverOptions.hmr, 'Component updates are only supported with HMR enabled.');
@@ -444,12 +479,13 @@ async function handleUpdate(
444479
let destroyAngularServerAppCalled = false;
445480

446481
// Invalidate any updated files
447-
for (const [file, { updated, type }] of generatedFiles) {
448-
if (!updated) {
482+
for (const [file, record] of generatedFiles) {
483+
if (!record.updated) {
449484
continue;
450485
}
486+
record.updated = false;
451487

452-
if (type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
488+
if (record.type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
453489
// Clear the server app cache
454490
// This must be done before module invalidation.
455491
const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs')) as {
@@ -541,85 +577,65 @@ async function handleUpdate(
541577
}
542578
}
543579

544-
function analyzeResultFiles(
580+
function updateResultRecord(
581+
outputPath: string,
582+
file: ResultFile,
545583
normalizePath: (id: string) => string,
546584
htmlIndexPath: string,
547-
resultFiles: Record<string, ResultFile>,
548585
generatedFiles: Map<string, OutputFileRecord>,
549586
componentStyles: Map<string, ComponentStyleRecord>,
550-
) {
551-
const seen = new Set<string>(['/index.html']);
552-
for (const [outputPath, file] of Object.entries(resultFiles)) {
553-
if (file.origin === 'disk') {
554-
continue;
555-
}
556-
let filePath;
557-
if (outputPath === htmlIndexPath) {
558-
// Convert custom index output path to standard index path for dev-server usage.
559-
// This mimics the Webpack dev-server behavior.
560-
filePath = '/index.html';
561-
} else {
562-
filePath = '/' + normalizePath(outputPath);
563-
}
564-
565-
seen.add(filePath);
566-
567-
const servable =
568-
file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media;
569-
570-
// Skip analysis of sourcemaps
571-
if (filePath.endsWith('.map')) {
572-
generatedFiles.set(filePath, {
573-
contents: file.contents,
574-
servable,
575-
size: file.contents.byteLength,
576-
hash: file.hash,
577-
type: file.type,
578-
updated: false,
579-
});
587+
initial = false,
588+
): void {
589+
if (file.origin === 'disk') {
590+
return;
591+
}
580592

581-
continue;
582-
}
593+
let filePath;
594+
if (outputPath === htmlIndexPath) {
595+
// Convert custom index output path to standard index path for dev-server usage.
596+
// This mimics the Webpack dev-server behavior.
597+
filePath = '/index.html';
598+
} else {
599+
filePath = '/' + normalizePath(outputPath);
600+
}
583601

584-
const existingRecord = generatedFiles.get(filePath);
585-
if (
586-
existingRecord &&
587-
existingRecord.size === file.contents.byteLength &&
588-
existingRecord.hash === file.hash
589-
) {
590-
// Same file
591-
existingRecord.updated = false;
592-
continue;
593-
}
602+
const servable =
603+
file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media;
594604

595-
// New or updated file
605+
// Skip analysis of sourcemaps
606+
if (filePath.endsWith('.map')) {
596607
generatedFiles.set(filePath, {
597608
contents: file.contents,
609+
servable,
598610
size: file.contents.byteLength,
599611
hash: file.hash,
600-
updated: true,
601612
type: file.type,
602-
servable,
613+
updated: false,
603614
});
604615

605-
// Record any external component styles
606-
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
607-
const componentStyle = componentStyles.get(filePath);
608-
if (componentStyle) {
609-
componentStyle.rawContent = file.contents;
610-
} else {
611-
componentStyles.set(filePath, {
612-
rawContent: file.contents,
613-
});
614-
}
615-
}
616+
return;
616617
}
617618

618-
// Clear stale output files
619-
for (const file of generatedFiles.keys()) {
620-
if (!seen.has(file)) {
621-
generatedFiles.delete(file);
622-
componentStyles.delete(file);
619+
// New or updated file
620+
generatedFiles.set(filePath, {
621+
contents: file.contents,
622+
size: file.contents.byteLength,
623+
hash: file.hash,
624+
// Consider the files updated except on the initial build result
625+
updated: !initial,
626+
type: file.type,
627+
servable,
628+
});
629+
630+
// Record any external component styles
631+
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
632+
const componentStyle = componentStyles.get(filePath);
633+
if (componentStyle) {
634+
componentStyle.rawContent = file.contents;
635+
} else {
636+
componentStyles.set(filePath, {
637+
rawContent: file.contents,
638+
});
623639
}
624640
}
625641
}

0 commit comments

Comments
 (0)