From 6d0ebdb0186d6ea3de86104f8cf6bea54f8f7bce Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Mon, 18 Mar 2024 11:26:09 +0000 Subject: [PATCH] fix(@angular-devkit/build-angular): only generate `server` directory when SSR is enabled Previously, the `server` directory was erroneously generated even in instances where SSG was enabled but SSR remained inactive. --- .../src/builders/application/build-action.ts | 59 +++++++++++-------- .../application/tests/options/ssr_spec.ts | 42 +++++++++++++ .../build_angular/src/tools/esbuild/utils.ts | 2 +- 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/application/build-action.ts b/packages/angular_devkit/build_angular/src/builders/application/build-action.ts index 4b6d15d955cc..44034dc47d70 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/build-action.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/build-action.ts @@ -21,7 +21,7 @@ import { import { deleteOutputDir } from '../../utils/delete-output-dir'; import { shouldWatchRoot } from '../../utils/environment-options'; import { NormalizedCachedOptions } from '../../utils/normalize-cache'; -import { NormalizedOutputOptions } from './options'; +import { NormalizedApplicationBuildOptions, NormalizedOutputOptions } from './options'; // Watch workspace for package manager changes const packageWatchFiles = [ @@ -37,6 +37,9 @@ const packageWatchFiles = [ '.pnp.data.json', ]; +type BuildActionOutput = (ExecutionResult['outputWithFiles'] | ExecutionResult['output']) & + BuilderOutput; + export async function* runEsBuildBuildAction( action: (rebuildState?: RebuildState) => Promise, options: { @@ -58,7 +61,7 @@ export async function* runEsBuildBuildAction( colors?: boolean; jsonLogs?: boolean; }, -): AsyncIterable<(ExecutionResult['outputWithFiles'] | ExecutionResult['output']) & BuilderOutput> { +): AsyncIterable { const { writeToFileSystemFilter, writeToFileSystem, @@ -153,16 +156,7 @@ export async function* runEsBuildBuildAction( // Output the first build results after setting up the watcher to ensure that any code executed // higher in the iterator call stack will trigger the watcher. This is particularly relevant for // unit tests which execute the builder and modify the file system programmatically. - if (writeToFileSystem) { - // Write output files - await writeResultFiles(result.outputFiles, result.assetFiles, outputOptions); - - yield result.output; - } else { - // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - yield result.outputWithFiles as any; - } + yield await writeAndEmitOutput(writeToFileSystem, result, outputOptions, writeToFileSystemFilter); // Finish if watch mode is not enabled if (!watcher) { @@ -212,19 +206,12 @@ export async function* runEsBuildBuildAction( watcher.remove([...staleWatchFiles]); } - if (writeToFileSystem) { - // Write output files - const filesToWrite = writeToFileSystemFilter - ? result.outputFiles.filter(writeToFileSystemFilter) - : result.outputFiles; - await writeResultFiles(filesToWrite, result.assetFiles, outputOptions); - - yield result.output; - } else { - // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - yield result.outputWithFiles as any; - } + yield await writeAndEmitOutput( + writeToFileSystem, + result, + outputOptions, + writeToFileSystemFilter, + ); } } finally { // Stop the watcher and cleanup incremental rebuild state @@ -233,3 +220,25 @@ export async function* runEsBuildBuildAction( shutdownSassWorkerPool(); } } + +async function writeAndEmitOutput( + writeToFileSystem: boolean, + { outputFiles, output, outputWithFiles, assetFiles }: ExecutionResult, + outputOptions: NormalizedApplicationBuildOptions['outputOptions'], + writeToFileSystemFilter: ((file: BuildOutputFile) => boolean) | undefined, +): Promise { + if (writeToFileSystem) { + // Write output files + const outputFilesToWrite = writeToFileSystemFilter + ? outputFiles.filter(writeToFileSystemFilter) + : outputFiles; + + await writeResultFiles(outputFilesToWrite, assetFiles, outputOptions); + + return output; + } else { + // Requires casting due to unneeded `JsonObject` requirement. Remove once fixed. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return outputWithFiles as any; + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/application/tests/options/ssr_spec.ts b/packages/angular_devkit/build_angular/src/builders/application/tests/options/ssr_spec.ts index 2caef3e3e14f..e629b4c37f2c 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/tests/options/ssr_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/tests/options/ssr_spec.ts @@ -50,5 +50,47 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { expect(result?.success).toBeTrue(); harness.expectFile('dist/server/server.mjs').toExist(); }); + + it(`should emit 'server' directory when 'ssr' is 'true'`, async () => { + await harness.writeFile('file.mjs', `console.log('Hello!');`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectDirectory('dist/server').toExist(); + }); + + it(`should not emit 'server' directory when 'ssr' is 'false'`, async () => { + await harness.writeFile('file.mjs', `console.log('Hello!');`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: false, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectDirectory('dist/server').toNotExist(); + }); + + it(`should not emit 'server' directory when 'ssr' is not set`, async () => { + await harness.writeFile('file.mjs', `console.log('Hello!');`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: undefined, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectDirectory('dist/server').toNotExist(); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts index 8c8da676839e..0bf31385fe03 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts @@ -217,7 +217,7 @@ export function getFeatureSupport(target: string[]): BuildOptions['supported'] { export async function writeResultFiles( outputFiles: BuildOutputFile[], assetFiles: BuildOutputAsset[] | undefined, - { base, browser, media, server }: NormalizedOutputOptions, + { base, browser, server }: NormalizedOutputOptions, ) { const directoryExists = new Set(); const ensureDirectoryExists = async (destPath: string) => {