From 40ff19720accf0ffb96315e3547c08cfc5adc1ba Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Sun, 19 Jan 2025 15:02:26 -0500 Subject: [PATCH] fix(@angular/build): allow asset changes to reload page on incremental updates Changes to asset files were previously not considered when determining if an incremental build result could be background update and not cause a page reload. Asset modifications will not cause a page reload in incremental update cases. Additionally, a superfluous component update result will no longer be emitted in this case. The required page reload will account for the component updates. --- .../src/builders/application/build-action.ts | 204 +++++++++--------- 1 file changed, 105 insertions(+), 99 deletions(-) diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts index f819ef4e097d..d3c1adcd4547 100644 --- a/packages/angular/build/src/builders/application/build-action.ts +++ b/packages/angular/build/src/builders/application/build-action.ts @@ -242,33 +242,11 @@ function* emitOutputResults( return; } - // Template updates only exist if no other JS changes have occurred - const hasTemplateUpdates = !!templateUpdates?.size; - if (hasTemplateUpdates) { - const updateResult: ComponentUpdateResult = { - kind: ResultKind.ComponentUpdate, - updates: Array.from(templateUpdates, ([id, content]) => ({ - type: 'template', - id, - content, - })), - }; - - yield updateResult; - } - - // Use an incremental result if previous output information is available - if (rebuildState && changes) { - const { previousAssetsInfo, previousOutputInfo } = rebuildState; - - const incrementalResult: IncrementalResult = { - kind: ResultKind.Incremental, + // Use a full result if there is no rebuild state (no prior build result) + if (!rebuildState || !changes) { + const result: FullResult = { + kind: ResultKind.Full, warnings: warnings as ResultMessage[], - // These files need to be updated in the dev server but should not signal any updates - background: hasTemplateUpdates, - added: [], - removed: [], - modified: [], files: {}, detail: { externalMetadata, @@ -277,78 +255,42 @@ function* emitOutputResults( outputOptions, }, }; - - // Initially assume all previous output files have been removed - const removedOutputFiles = new Map(previousOutputInfo); - for (const file of outputFiles) { - removedOutputFiles.delete(file.path); - - const previousHash = previousOutputInfo.get(file.path)?.hash; - let needFile = false; - if (previousHash === undefined) { - needFile = true; - incrementalResult.added.push(file.path); - } else if (previousHash !== file.hash) { - needFile = true; - incrementalResult.modified.push(file.path); - } - - if (needFile) { - // Updates to non-JS files must signal an update with the dev server - if (!/(?:\.m?js|\.map)?$/.test(file.path)) { - incrementalResult.background = false; - } - - incrementalResult.files[file.path] = { - type: file.type, - contents: file.contents, - origin: 'memory', - hash: file.hash, - }; - } - } - - // Initially assume all previous assets files have been removed - const removedAssetFiles = new Map(previousAssetsInfo); - for (const { source, destination } of assetFiles) { - removedAssetFiles.delete(source); - - if (!previousAssetsInfo.has(source)) { - incrementalResult.added.push(destination); - } else if (changes.modified.has(source)) { - incrementalResult.modified.push(destination); - } else { - continue; - } - - incrementalResult.files[destination] = { + for (const file of assetFiles) { + result.files[file.destination] = { type: BuildOutputFileType.Browser, - inputPath: source, + inputPath: file.source, origin: 'disk', }; } + for (const file of outputFiles) { + result.files[file.path] = { + type: file.type, + contents: file.contents, + origin: 'memory', + hash: file.hash, + }; + } - // Include the removed output and asset files - incrementalResult.removed.push( - ...Array.from(removedOutputFiles, ([file, { type }]) => ({ - path: file, - type, - })), - ...Array.from(removedAssetFiles.values(), (file) => ({ - path: file, - type: BuildOutputFileType.Browser, - })), - ); - - yield incrementalResult; + yield result; return; } - // Otherwise, use a full result - const result: FullResult = { - kind: ResultKind.Full, + // Template updates only exist if no other JS changes have occurred. + // A full page reload may be required based on the following incremental output change analysis. + const hasTemplateUpdates = !!templateUpdates?.size; + + // Use an incremental result if previous output information is available + const { previousAssetsInfo, previousOutputInfo } = rebuildState; + + const incrementalResult: IncrementalResult = { + kind: ResultKind.Incremental, warnings: warnings as ResultMessage[], + // Initially attempt to use a background update of files to support component updates. + background: hasTemplateUpdates, + added: [], + removed: [], + modified: [], files: {}, detail: { externalMetadata, @@ -357,21 +299,85 @@ function* emitOutputResults( outputOptions, }, }; - for (const file of assetFiles) { - result.files[file.destination] = { + + // Initially assume all previous output files have been removed + const removedOutputFiles = new Map(previousOutputInfo); + for (const file of outputFiles) { + removedOutputFiles.delete(file.path); + + const previousHash = previousOutputInfo.get(file.path)?.hash; + let needFile = false; + if (previousHash === undefined) { + needFile = true; + incrementalResult.added.push(file.path); + } else if (previousHash !== file.hash) { + needFile = true; + incrementalResult.modified.push(file.path); + } + + if (needFile) { + // Updates to non-JS files must signal an update with the dev server + if (!/(?:\.m?js|\.map)$/.test(file.path)) { + incrementalResult.background = false; + } + + incrementalResult.files[file.path] = { + type: file.type, + contents: file.contents, + origin: 'memory', + hash: file.hash, + }; + } + } + + // Initially assume all previous assets files have been removed + const removedAssetFiles = new Map(previousAssetsInfo); + for (const { source, destination } of assetFiles) { + removedAssetFiles.delete(source); + + if (!previousAssetsInfo.has(source)) { + incrementalResult.added.push(destination); + incrementalResult.background = false; + } else if (changes.modified.has(source)) { + incrementalResult.modified.push(destination); + incrementalResult.background = false; + } else { + continue; + } + + incrementalResult.files[destination] = { type: BuildOutputFileType.Browser, - inputPath: file.source, + inputPath: source, origin: 'disk', }; } - for (const file of outputFiles) { - result.files[file.path] = { - type: file.type, - contents: file.contents, - origin: 'memory', - hash: file.hash, + + // Include the removed output and asset files + incrementalResult.removed.push( + ...Array.from(removedOutputFiles, ([file, { type }]) => ({ + path: file, + type, + })), + ...Array.from(removedAssetFiles.values(), (file) => ({ + path: file, + type: BuildOutputFileType.Browser, + })), + ); + + yield incrementalResult; + + // If there are template updates and the incremental update was background only, a component + // update is possible. + if (hasTemplateUpdates && incrementalResult.background) { + const updateResult: ComponentUpdateResult = { + kind: ResultKind.ComponentUpdate, + updates: Array.from(templateUpdates, ([id, content]) => ({ + type: 'template', + id, + content, + })), }; - } - yield result; + yield updateResult; + } }