diff --git a/packages/angular/build/src/builders/dev-server/vite-server.ts b/packages/angular/build/src/builders/dev-server/vite-server.ts index fb70e6f964d5..bc8f0cae28b0 100644 --- a/packages/angular/build/src/builders/dev-server/vite-server.ts +++ b/packages/angular/build/src/builders/dev-server/vite-server.ts @@ -147,6 +147,8 @@ export async function* serveWithVite( browserOptions.templateUpdates = serverOptions.liveReload && serverOptions.hmr && useComponentTemplateHmr; + browserOptions.incrementalResults = true; + // Setup the prebundling transformer that will be shared across Vite prebundling requests const prebundleTransformer = new JavaScriptTransformer( // Always enable JIT linking to support applications built with and without AOT. @@ -225,10 +227,25 @@ export async function* serveWithVite( } assetFiles.clear(); - for (const [outputPath, file] of Object.entries(result.files)) { + componentStyles.clear(); + generatedFiles.clear(); + for (const entry of Object.entries(result.files)) { + const [outputPath, file] = entry; if (file.origin === 'disk') { assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath)); + continue; } + + updateResultRecord( + outputPath, + file, + normalizePath, + htmlIndexPath, + generatedFiles, + componentStyles, + // The initial build will not yet have a server setup + !server, + ); } // 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( // Clear stale template updates on code rebuilds templateUpdates.clear(); - // Analyze result files for changes - analyzeResultFiles( - normalizePath, - htmlIndexPath, - result.files, - generatedFiles, - componentStyles, - ); break; case ResultKind.Incremental: assert(server, 'Builder must provide an initial full build before incremental results.'); - // TODO: Implement support -- application builder currently does not use + + for (const removed of result.removed) { + const filePath = '/' + normalizePath(removed.path); + generatedFiles.delete(filePath); + assetFiles.delete(filePath); + } + for (const modified of result.modified) { + updateResultRecord( + modified, + result.files[modified], + normalizePath, + htmlIndexPath, + generatedFiles, + componentStyles, + ); + } + for (const added of result.added) { + updateResultRecord( + added, + result.files[added], + normalizePath, + htmlIndexPath, + generatedFiles, + componentStyles, + ); + } + break; case ResultKind.ComponentUpdate: assert(serverOptions.hmr, 'Component updates are only supported with HMR enabled.'); @@ -444,12 +479,13 @@ async function handleUpdate( let destroyAngularServerAppCalled = false; // Invalidate any updated files - for (const [file, { updated, type }] of generatedFiles) { - if (!updated) { + for (const [file, record] of generatedFiles) { + if (!record.updated) { continue; } + record.updated = false; - if (type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) { + if (record.type === BuildOutputFileType.ServerApplication && !destroyAngularServerAppCalled) { // Clear the server app cache // This must be done before module invalidation. const { ɵdestroyAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs')) as { @@ -541,85 +577,65 @@ async function handleUpdate( } } -function analyzeResultFiles( +function updateResultRecord( + outputPath: string, + file: ResultFile, normalizePath: (id: string) => string, htmlIndexPath: string, - resultFiles: Record, generatedFiles: Map, componentStyles: Map, -) { - const seen = new Set(['/index.html']); - for (const [outputPath, file] of Object.entries(resultFiles)) { - if (file.origin === 'disk') { - continue; - } - let filePath; - if (outputPath === htmlIndexPath) { - // Convert custom index output path to standard index path for dev-server usage. - // This mimics the Webpack dev-server behavior. - filePath = '/index.html'; - } else { - filePath = '/' + normalizePath(outputPath); - } - - seen.add(filePath); - - const servable = - file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media; - - // Skip analysis of sourcemaps - if (filePath.endsWith('.map')) { - generatedFiles.set(filePath, { - contents: file.contents, - servable, - size: file.contents.byteLength, - hash: file.hash, - type: file.type, - updated: false, - }); + initial = false, +): void { + if (file.origin === 'disk') { + return; + } - continue; - } + let filePath; + if (outputPath === htmlIndexPath) { + // Convert custom index output path to standard index path for dev-server usage. + // This mimics the Webpack dev-server behavior. + filePath = '/index.html'; + } else { + filePath = '/' + normalizePath(outputPath); + } - const existingRecord = generatedFiles.get(filePath); - if ( - existingRecord && - existingRecord.size === file.contents.byteLength && - existingRecord.hash === file.hash - ) { - // Same file - existingRecord.updated = false; - continue; - } + const servable = + file.type === BuildOutputFileType.Browser || file.type === BuildOutputFileType.Media; - // New or updated file + // Skip analysis of sourcemaps + if (filePath.endsWith('.map')) { generatedFiles.set(filePath, { contents: file.contents, + servable, size: file.contents.byteLength, hash: file.hash, - updated: true, type: file.type, - servable, + updated: false, }); - // Record any external component styles - if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) { - const componentStyle = componentStyles.get(filePath); - if (componentStyle) { - componentStyle.rawContent = file.contents; - } else { - componentStyles.set(filePath, { - rawContent: file.contents, - }); - } - } + return; } - // Clear stale output files - for (const file of generatedFiles.keys()) { - if (!seen.has(file)) { - generatedFiles.delete(file); - componentStyles.delete(file); + // New or updated file + generatedFiles.set(filePath, { + contents: file.contents, + size: file.contents.byteLength, + hash: file.hash, + // Consider the files updated except on the initial build result + updated: !initial, + type: file.type, + servable, + }); + + // Record any external component styles + if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) { + const componentStyle = componentStyles.get(filePath); + if (componentStyle) { + componentStyle.rawContent = file.contents; + } else { + componentStyles.set(filePath, { + rawContent: file.contents, + }); } } }