Skip to content

Commit bb206f9

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 9b57ff0 commit bb206f9

File tree

1 file changed

+89
-75
lines changed

1 file changed

+89
-75
lines changed

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

Lines changed: 89 additions & 75 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,24 @@ 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+
entry,
241+
normalizePath,
242+
htmlIndexPath,
243+
generatedFiles,
244+
componentStyles,
245+
// The initial build will not yet have a server setup
246+
!server,
247+
);
232248
}
233249

234250
// Invalidate SSR module graph to ensure that only new rebuild is used and not stale component updates
@@ -239,18 +255,34 @@ export async function* serveWithVite(
239255
// Clear stale template updates on code rebuilds
240256
templateUpdates.clear();
241257

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

446478
// Invalidate any updated files
447-
for (const [file, { updated, type }] of generatedFiles) {
448-
if (!updated) {
479+
for (const [file, record] of generatedFiles) {
480+
if (!record.updated) {
449481
continue;
450482
}
483+
record.updated = false;
451484

452-
if (type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
485+
if (record.type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) {
453486
// Clear the server app cache
454487
// This must be done before module invalidation.
455488
const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs')) as {
@@ -541,85 +574,66 @@ async function handleUpdate(
541574
}
542575
}
543576

544-
function analyzeResultFiles(
577+
function updateResultRecord(
578+
resultFile: [outpuPath: string, file: ResultFile],
545579
normalizePath: (id: string) => string,
546580
htmlIndexPath: string,
547-
resultFiles: Record<string, ResultFile>,
548581
generatedFiles: Map<string, OutputFileRecord>,
549582
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-
}
583+
initial = false,
584+
): void {
585+
const [outputPath, file] = resultFile;
564586

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+
if (file.origin === 'disk') {
588+
return;
589+
}
580590

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

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-
}
600+
const servable =
601+
file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media;
594602

595-
// New or updated file
603+
// Skip analysis of sourcemaps
604+
if (filePath.endsWith('.map')) {
596605
generatedFiles.set(filePath, {
597606
contents: file.contents,
607+
servable,
598608
size: file.contents.byteLength,
599609
hash: file.hash,
600-
updated: true,
601610
type: file.type,
602-
servable,
611+
updated: false,
603612
});
604613

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-
}
614+
return;
616615
}
617616

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);
617+
// New or updated file
618+
generatedFiles.set(filePath, {
619+
contents: file.contents,
620+
size: file.contents.byteLength,
621+
hash: file.hash,
622+
// Consider the files updated except on the initial build result
623+
updated: !initial,
624+
type: file.type,
625+
servable,
626+
});
627+
628+
// Record any external component styles
629+
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
630+
const componentStyle = componentStyles.get(filePath);
631+
if (componentStyle) {
632+
componentStyle.rawContent = file.contents;
633+
} else {
634+
componentStyles.set(filePath, {
635+
rawContent: file.contents,
636+
});
623637
}
624638
}
625639
}

0 commit comments

Comments
 (0)