Skip to content

Commit 3b9687a

Browse files
committed
fix(@angular/build): remove deleted assets from output during watch mode
This commit ensures that assets deleted from the source are also removed from the output directory while in watch mode. Previously, deleted assets could persist in the output folder, potentially causing inconsistencies or outdated files to be served. This fix improves the accuracy of the build output by maintaining synchronization between the source and the output directory during development.
1 parent 2f92121 commit 3b9687a

File tree

3 files changed

+84
-18
lines changed

3 files changed

+84
-18
lines changed

packages/angular/build/src/builders/application/build-action.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { BuildOutputFileType } from '../../tools/esbuild/bundler-context';
1313
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
1414
import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language';
1515
import { logMessages, withNoProgress, withSpinner } from '../../tools/esbuild/utils';
16+
import { ChangedFiles } from '../../tools/esbuild/watcher';
1617
import { shouldWatchRoot } from '../../utils/environment-options';
1718
import { NormalizedCachedOptions } from '../../utils/normalize-cache';
1819
import { NormalizedApplicationBuildOptions, NormalizedOutputOptions } from './options';
@@ -199,7 +200,8 @@ export async function* runEsBuildBuildAction(
199200
yield emitOutputResult(
200201
result,
201202
outputOptions,
202-
incrementalResults ? rebuildState.previousOutputInfo : undefined,
203+
changes,
204+
incrementalResults ? rebuildState : undefined,
203205
);
204206
}
205207
} finally {
@@ -222,7 +224,8 @@ function emitOutputResult(
222224
templateUpdates,
223225
}: ExecutionResult,
224226
outputOptions: NormalizedApplicationBuildOptions['outputOptions'],
225-
previousOutputInfo?: ReadonlyMap<string, { hash: string; type: BuildOutputFileType }>,
227+
changes?: ChangedFiles,
228+
rebuildState?: RebuildState,
226229
): Result {
227230
if (errors.length > 0) {
228231
return {
@@ -251,7 +254,9 @@ function emitOutputResult(
251254
}
252255

253256
// Use an incremental result if previous output information is available
254-
if (previousOutputInfo) {
257+
if (rebuildState && changes) {
258+
const { previousAssetsInfo, previousOutputInfo } = rebuildState;
259+
255260
const incrementalResult: IncrementalResult = {
256261
kind: ResultKind.Incremental,
257262
warnings: warnings as ResultMessage[],
@@ -269,7 +274,6 @@ function emitOutputResult(
269274

270275
// Initially assume all previous output files have been removed
271276
const removedOutputFiles = new Map(previousOutputInfo);
272-
273277
for (const file of outputFiles) {
274278
removedOutputFiles.delete(file.path);
275279

@@ -293,24 +297,37 @@ function emitOutputResult(
293297
}
294298
}
295299

296-
// Include the removed output files
300+
// Initially assume all previous assets files have been removed
301+
const removedAssetFiles = new Map(previousAssetsInfo);
302+
for (const { source, destination } of assetFiles) {
303+
removedAssetFiles.delete(source);
304+
305+
if (changes.modified.has(source)) {
306+
incrementalResult.modified.push(destination);
307+
} else if (!previousAssetsInfo.has(source)) {
308+
incrementalResult.added.push(destination);
309+
} else {
310+
continue;
311+
}
312+
313+
incrementalResult.files[destination] = {
314+
type: BuildOutputFileType.Browser,
315+
inputPath: source,
316+
origin: 'disk',
317+
};
318+
}
319+
320+
// Include the removed output and asset files
297321
incrementalResult.removed.push(
298322
...Array.from(removedOutputFiles, ([file, { type }]) => ({
299323
path: file,
300324
type,
301325
})),
302-
);
303-
304-
// Always consider asset files as added to ensure new/modified assets are available.
305-
// TODO: Consider more comprehensive asset analysis.
306-
for (const file of assetFiles) {
307-
incrementalResult.added.push(file.destination);
308-
incrementalResult.files[file.destination] = {
326+
...Array.from(removedAssetFiles.values(), (file) => ({
327+
path: file,
309328
type: BuildOutputFileType.Browser,
310-
inputPath: file.source,
311-
origin: 'disk',
312-
};
313-
}
329+
})),
330+
);
314331

315332
return incrementalResult;
316333
}

packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,50 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
6161

6262
expect(buildCount).toBe(2);
6363
});
64+
65+
it('remove deleted asset from output', async () => {
66+
await Promise.all([
67+
harness.writeFile('public/asset-two.txt', 'bar'),
68+
harness.writeFile('public/asset-one.txt', 'foo'),
69+
]);
70+
71+
harness.useTarget('build', {
72+
...BASE_OPTIONS,
73+
assets: [
74+
{
75+
glob: '**/*',
76+
input: 'public',
77+
},
78+
],
79+
watch: true,
80+
});
81+
82+
const buildCount = await harness
83+
.execute({ outputLogsOnFailure: false })
84+
.pipe(
85+
timeout(BUILD_TIMEOUT),
86+
concatMap(async ({ result }, index) => {
87+
switch (index) {
88+
case 0:
89+
expect(result?.success).toBeTrue();
90+
harness.expectFile('dist/browser/asset-one.txt').toExist();
91+
harness.expectFile('dist/browser/asset-two.txt').toExist();
92+
93+
await harness.removeFile('public/asset-two.txt');
94+
break;
95+
case 1:
96+
expect(result?.success).toBeTrue();
97+
harness.expectFile('dist/browser/asset-one.txt').toExist();
98+
harness.expectFile('dist/browser/asset-two.txt').toNotExist();
99+
break;
100+
}
101+
}),
102+
take(2),
103+
count(),
104+
)
105+
.toPromise();
106+
107+
expect(buildCount).toBe(2);
108+
});
64109
});
65110
});

packages/angular/build/src/tools/esbuild/bundler-execution-result.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export interface RebuildState {
2727
componentStyleBundler: ComponentStylesheetBundler;
2828
codeBundleCache?: SourceFileCache;
2929
fileChanges: ChangedFiles;
30-
previousOutputInfo: Map<string, { hash: string; type: BuildOutputFileType }>;
30+
previousOutputInfo: ReadonlyMap<string, { hash: string; type: BuildOutputFileType }>;
31+
previousAssetsInfo: ReadonlyMap<string, string>;
3132
templateUpdates?: Map<string, string>;
3233
}
3334

@@ -172,12 +173,15 @@ export class ExecutionResult {
172173
previousOutputInfo: new Map(
173174
this.outputFiles.map(({ path, hash, type }) => [path, { hash, type }]),
174175
),
176+
previousAssetsInfo: new Map(
177+
this.assetFiles.map(({ source, destination }) => [source, destination]),
178+
),
175179
templateUpdates: this.templateUpdates,
176180
};
177181
}
178182

179183
findChangedFiles(
180-
previousOutputHashes: Map<string, { hash: string; type: BuildOutputFileType }>,
184+
previousOutputHashes: ReadonlyMap<string, { hash: string; type: BuildOutputFileType }>,
181185
): Set<string> {
182186
const changed = new Set<string>();
183187
for (const file of this.outputFiles) {

0 commit comments

Comments
 (0)