diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts new file mode 100644 index 000000000000..6bf7763d2e4b --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { concatMap, count, take, timeout } from 'rxjs'; +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +/** + * Maximum time in milliseconds for single build/rebuild + * This accounts for CI variability. + */ +const BUILD_TIMEOUT = 10_000; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when input asset changes"', () => { + beforeEach(async () => { + // Application code is not needed for styles tests + await harness.writeFile('src/main.ts', 'console.log("TEST");'); + await harness.writeFile('public/asset.txt', 'foo'); + }); + + it('emits updated asset', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [ + { + glob: '**/*', + input: 'public', + }, + ], + watch: true, + }); + + const buildCount = await harness + .execute({ outputLogsOnFailure: false }) + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + switch (index) { + case 0: + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset.txt').content.toContain('foo'); + + await harness.writeFile('public/asset.txt', 'bar'); + break; + case 1: + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset.txt').content.toContain('bar'); + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts b/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts index 61e9c860faef..60fe2966c5c7 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts @@ -147,18 +147,20 @@ export class ExecutionResult { }; } - get watchFiles() { - // Bundler contexts internally normalize file dependencies - const files = this.rebuildContexts.typescriptContexts - .flatMap((context) => [...context.watchFiles]) - .concat(this.rebuildContexts.otherContexts.flatMap((context) => [...context.watchFiles])); - if (this.codeBundleCache?.referencedFiles) { + get watchFiles(): Readonly { + const { typescriptContexts, otherContexts } = this.rebuildContexts; + + return [ + // Bundler contexts internally normalize file dependencies. + ...typescriptContexts.flatMap((context) => [...context.watchFiles]), + ...otherContexts.flatMap((context) => [...context.watchFiles]), // These files originate from TS/NG and can have POSIX path separators even on Windows. // To ensure path comparisons are valid, all these paths must be normalized. - files.push(...this.codeBundleCache.referencedFiles.map(normalize)); - } - - return files.concat(this.extraWatchFiles); + ...(this.codeBundleCache?.referencedFiles?.map(normalize) ?? []), + // The assets source files. + ...this.assetFiles.map(({ source }) => source), + ...this.extraWatchFiles, + ]; } createRebuildState(fileChanges: ChangedFiles): RebuildState {