From 67cfa2037b55f189f30c2553e88ea5cf0ae4cc8c Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:11:33 -0400 Subject: [PATCH] refactor(@angular/build): eliminate NoopCompilation and introduce primary and secondary compilation contexts NoopCompilation previously served as a AngularCompilation placeholder to satisfy the compilation requirement on AngularCompilationContext in secondary contexts (such as polyfills and server main code), leading to redundant compiler option extraction and tsconfig loading. AngularCompilationContext is now an abstract base class defining the shared contract. PrimaryCompilationContext encapsulates the active AngularCompilation, manages the lifecycle state, and holds the resolved compiler options. SecondaryCompilationContext omits the compilation entirely, delegates readiness and compiler option resolution to the primary context, and provides no-op lifecycle management. Secondary builds now await primary completion and retrieve compiler options without initializing or invoking a separate compilation. --- .../src/builders/application/execute-build.ts | 7 +- packages/angular/build/src/private.ts | 5 +- .../compilation/angular-compilation_spec.ts | 39 ---- .../src/tools/angular/compilation/index.ts | 1 - .../angular/compilation/noop-compilation.ts | 58 ------ .../esbuild/angular/compilation-state.ts | 89 +++++++-- .../esbuild/angular/compilation-state_spec.ts | 178 ++++++++++++++++++ .../tools/esbuild/angular/compiler-plugin.ts | 40 ++-- 8 files changed, 280 insertions(+), 137 deletions(-) delete mode 100644 packages/angular/build/src/tools/angular/compilation/noop-compilation.ts create mode 100644 packages/angular/build/src/tools/esbuild/angular/compilation-state_spec.ts diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index 8fe488a8ff76..379566e4de4d 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -8,7 +8,10 @@ import { BuilderContext } from '@angular-devkit/architect'; import { createAngularCompilation } from '../../tools/angular/compilation'; -import { AngularCompilationContext } from '../../tools/esbuild/angular/compilation-state'; +import { + AngularCompilationContext, + PrimaryCompilationContext, +} from '../../tools/esbuild/angular/compilation-state'; import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache'; import { generateBudgetStats } from '../../tools/esbuild/budget-stats'; import { BundleContextResult, BundlerContext } from '../../tools/esbuild/bundler-context'; @@ -125,7 +128,7 @@ export async function executeBuild( !!options.jit, !options.serverEntryPoint, ); - angularCompilationContext = new AngularCompilationContext(angularCompilation); + angularCompilationContext = new PrimaryCompilationContext(angularCompilation); bundlerContexts = setupBundlerContexts( options, target, diff --git a/packages/angular/build/src/private.ts b/packages/angular/build/src/private.ts index 9225c2f9d686..c4295aca88bb 100644 --- a/packages/angular/build/src/private.ts +++ b/packages/angular/build/src/private.ts @@ -13,7 +13,8 @@ * their existence may change in any future version. */ -import { NoopCompilation, createAngularCompilation } from './tools/angular/compilation'; +import { createAngularCompilation } from './tools/angular/compilation'; +import { SecondaryCompilationContext } from './tools/esbuild/angular/compilation-state'; import { CompilerPluginOptions, createCompilerPlugin as internalCreateCompilerPlugin, @@ -52,7 +53,7 @@ export function createCompilerPlugin( return internalCreateCompilerPlugin( pluginOptions, pluginOptions.noopTypeScriptCompilation - ? new NoopCompilation() + ? new SecondaryCompilationContext() : () => createAngularCompilation(!!pluginOptions.jit, !!pluginOptions.browserOnlyBuild), new ComponentStylesheetBundler( styleOptions, diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts index ad28c31bcce5..5e520fbadef8 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts @@ -14,7 +14,6 @@ import { AngularCompilation, AngularCompilationResult, DiagnosticModes, - NoopCompilation, createAngularCompilation, } from './index'; @@ -59,44 +58,6 @@ describe('AngularCompilation', () => { expect(diagnostics).toEqual({}); }); - describe('NoopCompilation', () => { - it('initializes with empty referencedFiles and compiler options', async () => { - const compilation = new NoopCompilation(); - const mockHostOptions = {} as AngularHostOptions; - const result = await compilation.initialize('tsconfig.json', mockHostOptions); - - expect(result.referencedFiles).toEqual([]); - expect(result.compilerOptions).toBeDefined(); - }); - - it('initializes with CompilerOptionOverrides object', async () => { - const compilation = new NoopCompilation(); - const mockHostOptions = {} as AngularHostOptions; - const result = await compilation.initialize('tsconfig.json', mockHostOptions, { - sourcemap: true, - enableHmr: true, - }); - - expect(result.referencedFiles).toEqual([]); - expect(result.compilerOptions.inlineSources).toBe(true); - expect(result.compilerOptions.inlineSourceMap).toBe(true); - expect(result.compilerOptions['_enableHmr']).toBe(true); - }); - - it('throws when calling emitAffectedFiles', () => { - const compilation = new NoopCompilation(); - expect(() => compilation.emitAffectedFiles()).toThrowError( - 'Not available when using noop compilation.', - ); - }); - - it('returns empty diagnostics from diagnoseFiles', async () => { - const compilation = new NoopCompilation(); - const diagnostics = await compilation.diagnoseFiles(); - expect(diagnostics).toEqual({}); - }); - }); - describe('TypeScriptCompilation', () => { class MockTypeScriptCompilation extends TypeScriptCompilation { async initialize(): Promise { diff --git a/packages/angular/build/src/tools/angular/compilation/index.ts b/packages/angular/build/src/tools/angular/compilation/index.ts index 39391bcfb562..268abec678ca 100644 --- a/packages/angular/build/src/tools/angular/compilation/index.ts +++ b/packages/angular/build/src/tools/angular/compilation/index.ts @@ -16,4 +16,3 @@ export { } from './angular-compilation'; export type { CompilerOptionOverrides } from './compiler-options'; export { createAngularCompilation, type AngularCompilationMode } from './factory'; -export { NoopCompilation } from './noop-compilation'; diff --git a/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts b/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts deleted file mode 100644 index 7ace166d90fd..000000000000 --- a/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @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 { AngularHostOptions } from '../angular-host'; -import { AngularCompilation, AngularCompilationResult } from './angular-compilation'; -import type { CompilerOptionOverrides } from './compiler-options'; - -/** - * An Angular compilation that performs no actual compilation or code emission. - * Used for secondary compilation contexts where only the resolved compiler options - * and configuration state are needed. - */ -export class NoopCompilation extends AngularCompilation { - async initialize( - tsconfig: string, - hostOptions: AngularHostOptions, - compilerOptionOverrides?: CompilerOptionOverrides, - ): Promise { - // Load the compiler configuration - const { options: originalCompilerOptions } = await this.loadConfiguration(tsconfig); - // Apply relevant overrides directly without invoking `transformCompilerOptions` - // to avoid loading the `typescript` package on the main thread. - const compilerOptions = { - ...originalCompilerOptions, - noEmitOnError: false, - composite: false, - inlineSources: !!compilerOptionOverrides?.sourcemap, - inlineSourceMap: !!compilerOptionOverrides?.sourcemap, - sourceMap: undefined, - mapRoot: undefined, - sourceRoot: undefined, - preserveSymlinks: compilerOptionOverrides?.preserveSymlinks, - externalRuntimeStyles: compilerOptionOverrides?.externalRuntimeStyles, - _enableHmr: !!compilerOptionOverrides?.enableHmr, - _useTypeScriptTranspilation: - !originalCompilerOptions.isolatedModules || - !!compilerOptionOverrides?.instrumentForCoverage, - supportTestBed: !!compilerOptionOverrides?.includeTestMetadata, - supportJitMode: !!compilerOptionOverrides?.includeTestMetadata, - customConditions: - originalCompilerOptions.moduleResolution === 100 /* Bundler */ || - originalCompilerOptions.module === 200 /* Preserve */ - ? compilerOptionOverrides?.customConditions - : originalCompilerOptions.customConditions, - }; - - return { compilerOptions, referencedFiles: [] }; - } - - override emitAffectedFiles(): never { - throw new Error('Not available when using noop compilation.'); - } -} diff --git a/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts b/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts index 286411a06057..541f52bbca76 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compilation-state.ts @@ -6,24 +6,46 @@ * found in the LICENSE file at https://angular.dev/license */ -import { type AngularCompilation, NoopCompilation } from '../../angular/compilation'; +import type { CompilerOptions } from '@angular/compiler-cli'; +import type { AngularCompilation } from '../../angular/compilation'; -export class AngularCompilationContext { - #compilation: AngularCompilation; +export abstract class AngularCompilationContext { + abstract readonly compilation?: AngularCompilation; + abstract isPrimary(): this is PrimaryCompilationContext; + abstract readonly waitUntilReady: Promise; + abstract getCompilerOptions(): Promise; + abstract dispose(): Promise; + + createSecondaryContext(): AngularCompilationContext { + return new SecondaryCompilationContext(this); + } +} + +export class PrimaryCompilationContext extends AngularCompilationContext { + readonly #compilation: AngularCompilation; #pendingCompilation = true; #resolveCompilationReady: ((value: boolean) => void) | undefined; #compilationReadyPromise: Promise | undefined; #hasErrors = true; + #compilerOptions: CompilerOptions | undefined; + #resolveCompilerOptions: ((options: CompilerOptions) => void) | undefined; + #compilerOptionsPromise: Promise | undefined; + constructor(compilation: AngularCompilation) { + super(); this.#compilation = compilation; } - get compilation(): AngularCompilation { + override isPrimary(): this is PrimaryCompilationContext { + return true; + } + + override get compilation(): AngularCompilation { return this.#compilation; } - get waitUntilReady(): Promise { + override get waitUntilReady(): Promise { if (!this.#pendingCompilation) { return Promise.resolve(this.#hasErrors); } @@ -35,20 +57,51 @@ export class AngularCompilationContext { return this.#compilationReadyPromise; } + override getCompilerOptions(): Promise { + if (this.#compilerOptions) { + return Promise.resolve(this.#compilerOptions); + } + + if (!this.#pendingCompilation) { + return Promise.resolve({}); + } + + this.#compilerOptionsPromise ??= new Promise((resolve) => { + this.#resolveCompilerOptions = resolve; + }); + + return this.#compilerOptionsPromise; + } + + setCompilerOptions(options: CompilerOptions): void { + this.#compilerOptions = options; + this.#resolveCompilerOptions?.(options); + this.#resolveCompilerOptions = undefined; + this.#compilerOptionsPromise = undefined; + } + markAsReady(hasErrors: boolean): void { this.#hasErrors = hasErrors; this.#resolveCompilationReady?.(hasErrors); + this.#resolveCompilationReady = undefined; this.#compilationReadyPromise = undefined; this.#pendingCompilation = false; + + if (this.#resolveCompilerOptions) { + this.#resolveCompilerOptions(this.#compilerOptions ?? {}); + this.#resolveCompilerOptions = undefined; + this.#compilerOptionsPromise = undefined; + } } markAsInProgress(): void { this.#pendingCompilation = true; + this.#compilerOptions = undefined; } #disposal: Promise | undefined; - dispose(): Promise { + override dispose(): Promise { // Reuse any in progress disposal to ensure all callers can await completion return (this.#disposal ??= this.#close()); } @@ -61,27 +114,27 @@ export class AngularCompilationContext { // Suppress closure errors to avoid unhandled rejections during teardown. } } +} - createSecondaryContext(): AngularCompilationContext { - return new SecondaryCompilationContext(this); +export class SecondaryCompilationContext extends AngularCompilationContext { + constructor(private readonly primaryContext?: AngularCompilationContext) { + super(); } -} -class SecondaryCompilationContext extends AngularCompilationContext { - constructor(private primaryContext: AngularCompilationContext) { - super(new NoopCompilation()); + override isPrimary(): this is PrimaryCompilationContext { + return false; } - override get waitUntilReady(): Promise { - return this.primaryContext.waitUntilReady; + override get compilation(): undefined { + return undefined; } - override markAsReady(hasErrors: boolean): void { - // No-op: secondary contexts do not control compilation state + override get waitUntilReady(): Promise { + return this.primaryContext?.waitUntilReady ?? Promise.resolve(false); } - override markAsInProgress(): void { - // No-op: secondary contexts do not control compilation state + override getCompilerOptions(): Promise { + return this.primaryContext?.getCompilerOptions() ?? Promise.resolve({}); } override async dispose(): Promise { diff --git a/packages/angular/build/src/tools/esbuild/angular/compilation-state_spec.ts b/packages/angular/build/src/tools/esbuild/angular/compilation-state_spec.ts new file mode 100644 index 000000000000..8007427d497c --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/angular/compilation-state_spec.ts @@ -0,0 +1,178 @@ +/** + * @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 type { CompilerOptions } from '@angular/compiler-cli'; +import type { AngularCompilation } from '../../angular/compilation'; +import { PrimaryCompilationContext, SecondaryCompilationContext } from './compilation-state'; + +describe('compilation-state', () => { + let mockCompilation: jasmine.SpyObj; + + beforeEach(() => { + mockCompilation = jasmine.createSpyObj('AngularCompilation', [ + 'initialize', + 'close', + ]); + }); + + describe('PrimaryCompilationContext', () => { + it('identifies as primary and provides the underlying compilation', () => { + const context = new PrimaryCompilationContext(mockCompilation); + + expect(context.isPrimary()).toBe(true); + expect(context.compilation).toBe(mockCompilation); + }); + + it('resolves getCompilerOptions after setCompilerOptions is called', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + const options: CompilerOptions = { target: 9, allowJs: true }; + + context.setCompilerOptions(options); + + const resolved = await context.getCompilerOptions(); + expect(resolved).toEqual(options); + }); + + it('resolves pending getCompilerOptions when setCompilerOptions is called later', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + const options: CompilerOptions = { target: 9, allowJs: true }; + + const optionsPromise = context.getCompilerOptions(); + context.setCompilerOptions(options); + + const resolved = await optionsPromise; + expect(resolved).toEqual(options); + }); + + it('manages ready state lifecycle correctly', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + + // Initially in progress + const readyPromise = context.waitUntilReady; + context.markAsReady(false); + + const hasErrors = await readyPromise; + expect(hasErrors).toBe(false); + + // When already ready, immediately returns error state + expect(await context.waitUntilReady).toBe(false); + + // Mark in progress again + context.markAsInProgress(); + const secondReadyPromise = context.waitUntilReady; + context.markAsReady(true); + + expect(await secondReadyPromise).toBe(true); + }); + + it('closes compilation and marks ready with errors on dispose', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + + const readyPromise = context.waitUntilReady; + await context.dispose(); + + expect(mockCompilation.close).toHaveBeenCalledTimes(1); + expect(await readyPromise).toBe(true); + }); + + it('unblocks pending getCompilerOptions on markAsReady error', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + + const optionsPromise = context.getCompilerOptions(); + context.markAsReady(true); + + const options = await optionsPromise; + expect(options).toEqual({}); + }); + + it('returns empty options from getCompilerOptions when compilation is not pending and options are unset', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + + context.markAsReady(false); + + const options = await context.getCompilerOptions(); + expect(options).toEqual({}); + }); + + it('clears cached compiler options on markAsInProgress to wait for fresh options on rebuild', async () => { + const context = new PrimaryCompilationContext(mockCompilation); + const initialOptions: CompilerOptions = { target: 9, allowJs: false }; + context.setCompilerOptions(initialOptions); + context.markAsReady(false); + + expect(await context.getCompilerOptions()).toEqual(initialOptions); + + // Rebuild starts + context.markAsInProgress(); + + // Pending call should wait for new options rather than returning stale ones + const nextOptionsPromise = context.getCompilerOptions(); + const updatedOptions: CompilerOptions = { target: 10, allowJs: true }; + context.setCompilerOptions(updatedOptions); + + expect(await nextOptionsPromise).toEqual(updatedOptions); + }); + + it('creates a linked SecondaryCompilationContext', () => { + const context = new PrimaryCompilationContext(mockCompilation); + const secondary = context.createSecondaryContext(); + + expect(secondary).toBeInstanceOf(SecondaryCompilationContext); + expect(secondary.isPrimary()).toBe(false); + expect(secondary.compilation).toBeUndefined(); + }); + }); + + describe('SecondaryCompilationContext', () => { + it('identifies as non-primary and has undefined compilation', () => { + const primary = new PrimaryCompilationContext(mockCompilation); + const secondary = primary.createSecondaryContext(); + + expect(secondary.isPrimary()).toBe(false); + expect(secondary.compilation).toBeUndefined(); + }); + + it('delegates waitUntilReady to primary context', async () => { + const primary = new PrimaryCompilationContext(mockCompilation); + const secondary = primary.createSecondaryContext(); + + const secondaryReadyPromise = secondary.waitUntilReady; + primary.markAsReady(false); + + expect(await secondaryReadyPromise).toBe(false); + }); + + it('delegates getCompilerOptions to primary context', async () => { + const primary = new PrimaryCompilationContext(mockCompilation); + const secondary = primary.createSecondaryContext(); + const options: CompilerOptions = { target: 9, allowJs: false }; + + primary.setCompilerOptions(options); + + expect(await secondary.getCompilerOptions()).toEqual(options); + }); + + it('does not dispose primary compilation when secondary is disposed', async () => { + const primary = new PrimaryCompilationContext(mockCompilation); + const secondary = primary.createSecondaryContext(); + + await secondary.dispose(); + + expect(mockCompilation.close).not.toHaveBeenCalled(); + }); + + it('handles standalone instantiation with sensible defaults', async () => { + const standalone = new SecondaryCompilationContext(); + + expect(standalone.isPrimary()).toBe(false); + expect(standalone.compilation).toBeUndefined(); + expect(await standalone.waitUntilReady).toBe(false); + expect(await standalone.getCompilerOptions()).toEqual({}); + }); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index 05c49040b43c..5ce1130d093a 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -23,12 +23,12 @@ import * as path from 'node:path'; import { maxWorkers, useTypeChecking } from '../../../utils/environment-options'; import { calculateHash, initializeHash } from '../../../utils/hash'; import { AngularHostOptions } from '../../angular/angular-host'; -import { AngularCompilation, DiagnosticModes, NoopCompilation } from '../../angular/compilation'; +import { AngularCompilation, DiagnosticModes } from '../../angular/compilation'; import { type PersistentCacheStore, createPersistentCacheStore } from '../cache'; import { JavaScriptTransformer } from '../javascript-transformer'; import { LoadResultCache, createCachedLoad } from '../load-result-cache'; import { logCumulativeDurations, profileAsync, resetCumulativeDurations } from '../profiling'; -import { AngularCompilationContext } from './compilation-state'; +import { AngularCompilationContext, PrimaryCompilationContext } from './compilation-state'; import { ComponentStylesheetBundler } from './component-stylesheets'; import { FileReferenceTracker } from './file-reference-tracker'; import { setupJitPluginCallbacks } from './jit-plugin-callbacks'; @@ -117,12 +117,12 @@ export function createCompilerPlugin( const angularCompilationContext = compilationContextOrCompilation instanceof AngularCompilationContext ? compilationContextOrCompilation - : new AngularCompilationContext( + : new PrimaryCompilationContext( typeof compilationContextOrCompilation === 'function' ? await compilationContextOrCompilation() : compilationContextOrCompilation, ); - const compilation: AngularCompilation = angularCompilationContext.compilation; + const compilation: AngularCompilation | undefined = angularCompilationContext.compilation; // The in-memory cache of TypeScript file outputs will be used during the build in `onLoad` callbacks for TS files. // A string value indicates direct TS/NG output and a Uint8Array indicates fully transformed code. @@ -150,12 +150,23 @@ export function createCompilerPlugin( // eslint-disable-next-line max-lines-per-function build.onStart(async () => { await initializeHash(); - angularCompilationContext.markAsInProgress(); const result: OnStartResult = { warnings: setupWarnings, }; + if (!angularCompilationContext.isPrimary()) { + hasCompilationErrors = await angularCompilationContext.waitUntilReady; + const compilerOptions = await angularCompilationContext.getCompilerOptions(); + shouldTsIgnoreJs = !compilerOptions.allowJs; + useTypeScriptTranspilation = !!compilerOptions['_useTypeScriptTranspilation']; + + return result; + } + + angularCompilationContext.markAsInProgress(); + const compilation = angularCompilationContext.compilation; + // Reset debug performance tracking resetCumulativeDurations(); @@ -163,10 +174,7 @@ export function createCompilerPlugin( // Angular compiler which does not have direct knowledge of transitive resource // dependencies or web worker processing. let modifiedFiles; - if ( - !(compilation instanceof NoopCompilation) && - pluginOptions.sourceFileCache?.modifiedFiles.size - ) { + if (pluginOptions.sourceFileCache?.modifiedFiles.size) { // TODO: Differentiate between changed input files and stale output files modifiedFiles = referencedFileTracker.update(pluginOptions.sourceFileCache.modifiedFiles); pluginOptions.sourceFileCache.invalidate(modifiedFiles); @@ -320,6 +328,7 @@ export function createCompilerPlugin( if (initializationResult.warnings?.length) { setupWarnings?.push(...initializationResult.warnings); } + angularCompilationContext.setCompilerOptions(initializationResult.compilerOptions); shouldTsIgnoreJs = !initializationResult.compilerOptions.allowJs; useTypeScriptTranspilation = !!initializationResult.compilerOptions['_useTypeScriptTranspilation']; @@ -345,12 +354,7 @@ export function createCompilerPlugin( // Initialization failure prevents further compilation steps hasCompilationErrors = true; - - return result; - } - - if (compilation instanceof NoopCompilation) { - hasCompilationErrors = await angularCompilationContext.waitUntilReady; + angularCompilationContext.markAsReady(true); return result; } @@ -444,7 +448,7 @@ export function createCompilerPlugin( let contents = typeScriptFileCache.get(request); let directContents: string | undefined; - if (contents === undefined && compilation.transformFile) { + if (contents === undefined && compilation?.transformFile) { try { directContents = await readFile(request, 'utf-8'); const transformResult = await compilation.transformFile(request, directContents); @@ -607,7 +611,9 @@ export function createCompilerPlugin( build.onEnd((result) => { // Ensure other compilations are unblocked if the main compilation throws during start - angularCompilationContext.markAsReady(hasCompilationErrors); + if (angularCompilationContext.isPrimary()) { + angularCompilationContext.markAsReady(hasCompilationErrors); + } for (const { outputFiles, metafile } of additionalResults.values()) { // Add any additional output files to the main output files