diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts index 102c19643397..f913cd70273e 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts @@ -22,6 +22,16 @@ import { import { basename, extname, relative } from 'node:path'; import { FileInfo } from '../../utils/index-file/augment-index-html'; +export type BundleContextResult = + | { errors: Message[]; warnings: Message[] } + | { + errors: undefined; + warnings: Message[]; + metafile: Metafile; + outputFiles: OutputFile[]; + initialFiles: FileInfo[]; + }; + /** * Determines if an unknown value is an esbuild BuildFailure error object thrown by esbuild. * @param value A potential esbuild BuildFailure error object. @@ -43,6 +53,50 @@ export class BundlerContext { }; } + static async bundleAll(contexts: Iterable): Promise { + const individualResults = await Promise.all([...contexts].map((context) => context.bundle())); + + // Return directly if only one result + if (individualResults.length === 1) { + return individualResults[0]; + } + + let errors: Message[] | undefined; + const warnings: Message[] = []; + const metafile: Metafile = { inputs: {}, outputs: {} }; + const initialFiles = []; + const outputFiles = []; + for (const result of individualResults) { + warnings.push(...result.warnings); + if (result.errors) { + errors ??= []; + errors.push(...result.errors); + continue; + } + + // Combine metafiles used for the stats option as well as bundle budgets and console output + if (result.metafile) { + metafile.inputs = { ...metafile.inputs, ...result.metafile.inputs }; + metafile.outputs = { ...metafile.outputs, ...result.metafile.outputs }; + } + + initialFiles.push(...result.initialFiles); + outputFiles.push(...result.outputFiles); + } + + if (errors !== undefined) { + return { errors, warnings }; + } + + return { + errors, + warnings, + metafile, + initialFiles, + outputFiles, + }; + } + /** * Executes the esbuild build function and normalizes the build result in the event of a * build failure that results in no output being generated. @@ -52,16 +106,7 @@ export class BundlerContext { * @returns If output files are generated, the full esbuild BuildResult; if not, the * warnings and errors for the attempted build. */ - async bundle(): Promise< - | { errors: Message[]; warnings: Message[] } - | { - errors: undefined; - warnings: Message[]; - metafile: Metafile; - outputFiles: OutputFile[]; - initialFiles: FileInfo[]; - } - > { + async bundle(): Promise { let result; try { if (this.#esbuildContext) { diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts index b14e2cfdf58e..e595e0247538 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts @@ -108,6 +108,9 @@ async function execute( const target = transformSupportedBrowsersToTargets(browsers); // Reuse rebuild state or create new bundle contexts for code and global stylesheets + const bundlerContexts = []; + + // Application code const codeBundleCache = options.watch ? rebuildState?.codeBundleCache ?? new SourceFileCache() : undefined; @@ -118,37 +121,38 @@ async function execute( !!options.watch, createCodeBundleOptions(options, target, browsers, codeBundleCache), ); - const globalStylesBundleContext = - rebuildState?.globalStylesRebuild ?? - new BundlerContext( + bundlerContexts.push(codeBundleContext); + // Global Stylesheets + let globalStylesBundleContext; + if (options.globalStyles.length > 0) { + globalStylesBundleContext = + rebuildState?.globalStylesRebuild ?? + new BundlerContext( + workspaceRoot, + !!options.watch, + createGlobalStylesBundleOptions( + options, + target, + browsers, + codeBundleCache?.loadResultCache, + ), + ); + bundlerContexts.push(globalStylesBundleContext); + } + // Global Scripts + if (options.globalScripts.length > 0) { + const globalScriptsBundleContext = new BundlerContext( workspaceRoot, !!options.watch, - createGlobalStylesBundleOptions(options, target, browsers, codeBundleCache?.loadResultCache), + createGlobalScriptsBundleOptions(options), ); + bundlerContexts.push(globalScriptsBundleContext); + } - const globalScriptsBundleContext = new BundlerContext( - workspaceRoot, - !!options.watch, - createGlobalScriptsBundleOptions(options), - ); - - const [codeResults, styleResults, scriptResults] = await Promise.all([ - // Execute esbuild to bundle the application code - codeBundleContext.bundle(), - // Execute esbuild to bundle the global stylesheets - globalStylesBundleContext.bundle(), - globalScriptsBundleContext.bundle(), - ]); + const bundlingResult = await BundlerContext.bundleAll(bundlerContexts); // Log all warnings and errors generated during bundling - await logMessages(context, { - errors: [ - ...(codeResults.errors || []), - ...(styleResults.errors || []), - ...(scriptResults.errors || []), - ], - warnings: [...codeResults.warnings, ...styleResults.warnings, ...scriptResults.warnings], - }); + await logMessages(context, bundlingResult); const executionResult = new ExecutionResult( codeBundleContext, @@ -157,40 +161,22 @@ async function execute( ); // Return if the bundling has errors - if (codeResults.errors || styleResults.errors || scriptResults.errors) { + if (bundlingResult.errors) { return executionResult; } - // Filter global stylesheet initial files - styleResults.initialFiles = styleResults.initialFiles.filter( - ({ name }) => options.globalStyles.find((style) => style.name === name)?.initial, - ); + // Filter global stylesheet initial files. Currently all initial CSS files are from the global styles option. + if (options.globalScripts.length > 0) { + bundlingResult.initialFiles = bundlingResult.initialFiles.filter( + ({ file, name }) => + !file.endsWith('.css') || + options.globalStyles.find((style) => style.name === name)?.initial, + ); + } - // Combine the bundling output files - const initialFiles: FileInfo[] = [ - ...codeResults.initialFiles, - ...styleResults.initialFiles, - ...scriptResults.initialFiles, - ]; - executionResult.outputFiles.push( - ...codeResults.outputFiles, - ...styleResults.outputFiles, - ...scriptResults.outputFiles, - ); + const { metafile, initialFiles, outputFiles } = bundlingResult; - // Combine metafiles used for the stats option as well as bundle budgets and console output - const metafile = { - inputs: { - ...codeResults.metafile?.inputs, - ...styleResults.metafile?.inputs, - ...scriptResults.metafile?.inputs, - }, - outputs: { - ...codeResults.metafile?.outputs, - ...styleResults.metafile?.outputs, - ...scriptResults.metafile?.outputs, - }, - }; + executionResult.outputFiles.push(...outputFiles); // Check metafile for CommonJS module usage if optimizing scripts if (optimizationOptions.scripts) {